Properties是Hashtable子类因Java 1.0无Map接口,只能复用线程安全的Hashtable存String键值,靠文档和重写方法约束类型,属历史包袱;load/store默认ISO-8859-1编码致中文乱码,应改用load(Reader)或loadFromXML();getProperty与get行为不一致且null处理不同;其synchronized方法不保证业务级线程安全,现代应用宜用不可变配置对象替代。

Properties 为什么是 Hashtable 的子类
因为 Java 1.0 时代没有 Map 接口,Hashtable 是当时唯一的线程安全键值容器。设计者直接复用它来存配置项——key 和 value 都是 String,但 Hashtable 要求泛型为 Object,于是靠文档和重写方法(如 setProperty)强行约束类型。
这不是权衡后的选择,是历史包袱:1995 年连接口都还没抽象出统一的 Map,更别说泛型了。后来加了 Map 接口,但为了二进制兼容,Properties 不能改继承关系。
load() 方法默认只支持 ISO-8859-1 编码
读取含中文的配置文件时,load(InputStream) 会把非 Latin-1 字符解码成乱码,哪怕文件本身是 UTF-8。这是最常踩的坑。
- 用
load(Reader)替代:传入new InputStreamReader(in, "UTF-8") - 或改用
loadFromXML(),它原生支持 UTF-8,且格式更规范 -
store()同理,默认也按 ISO-8859-1 写,中文会变问号
getProperty() 和 get() 行为不一致
getProperty(String key) 返回 String 或 null;而继承自 Hashtable 的 get(Object key) 返回 Object,且对 null key 抛 NullPointerException ——但 Properties 实际允许 key 为 null(只是不推荐)。
立即学习“Java免费学习笔记(深入)”;
更隐蔽的问题是:如果配置里写的是 debug=true,getProperty("debug") 返回 "true",但 get("debug") 也返回 "true"(因为底层还是 Hashtable.get())。表面一样,可一旦你用 put("debug", Boolean.TRUE) 手动塞了个非字符串值进去,getProperty() 就返回 null,而 get() 还能取到。
线程安全是假象,实际并不适合并发修改
Properties 继承 Hashtable,所有 public 方法都 synchronized,看起来线程安全。但真实场景中,配置读多写少,且「读取后解析逻辑」往往跨多个 getProperty() 调用——比如先读 db.url,再读 db.user,中间若被其他线程 reload,状态就可能不一致。
- 别依赖它的 synchronized 做业务同步,reload 本身不是原子操作
- 现代应用应把配置抽象为不可变对象(如用
Config类封装),加载完就冻结 - 如果真要热更新,用
ConcurrentHashMap+ 显式锁,而不是靠Properties的老式同步
它从设计第一天起就不是为动态配置服务的,只是个带换行/注释支持的字符串字典。现在还用它做核心配置管理,等于在用锤子拧螺丝——能转,但吃力,还容易滑丝。










