#Java #源码分析 #避坑指南 #面试题
摘要:
明明向
Properties中存入了数值,用get()探寻时它尚在,换作getProperty()却凭空消失,只返还一个冷冰冰的null。 这并非 JDK 的 Bug,也不是什么玄学。本文将以此为引,拨开java.util.Properties的源码迷雾,一探这段因“继承滥用”而尘封已久的设计公案。
大家好,我是 Re-Zero。
今天在 Code Review,我见到了一段足以写入《Java 迷惑行为大赏》的代码:某位兄弟图省事,直接用 put 方法把数字往 Properties 对象里塞,取的时候却遭遇“左右手互搏”——一只手能拿到,另一只手却摸了个空。
咱们来还原一下这个让新手摸不着头脑的“灵异现场”。
一、 案发现场:一次投币,两种人生
我们写一段最简单的测试代码:
Properties props = new Properties();
// 1. 注意:为了省事,这里直接存了个整数 88
// 这里的 88 是 Integer 类型,不是 String
props.put("timeout", 88);
// 2. 使用 Map 原生的 get() 方法
System.out.println("get: " + props.get("timeout"));
// 3. 使用 Properties 专属的 getProperty() 方法
System.out.println("getProperty: " + props.getProperty("timeout"));
按照正常逻辑,这就好比你往钱包里塞了一张钱,用左手掏出来是钱,用右手掏出来应该是空气吗?显然不合理。
但 Java 偏偏就给你变了个魔术,运行结果如下:
get: 88
getProperty: null <-- 见鬼了?!
明明数据就在里面,用 get 都能拿到,凭什么 getProperty 就视而不见?难道它还歧视整数?
二、 源码解剖:寻找失落的真相
为了搞清楚原因,我们直接点开 java.util.Properties 的源码,看看 getProperty 到底背着我们干了什么。
代码非常短,但每一行都藏着心眼:
public String getProperty(String key) {
// 1. 先调用父类 (Hashtable) 的 get 方法,拿到 value
Object oval = super.get(key);
// 2. 关键判决!
// 检查拿到的 value 是不是 String 类型
// 如果是 String,强转;如果不是,sval 直接置为 null
String sval = (oval instanceof String) ? (String)oval : null;
// 3. 兜底逻辑:如果是 null,且存在默认配置集 (defaults),则去默认集里找
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
}
破案了,凶手就是这行代码:
String sval = (oval instanceof String) ? (String)oval : null;
getProperty 方法是一个有着严重类型洁癖的方法:
-
它强制要求:Key 和 Value 必须都是
String类型。 -
在我们的案例中,存入的是
Integer类型的88。 -
oval instanceof String判定为假。 -
所以变量
sval被无情地赋值为null。
那为什么普通的 get() 方法能拿到呢?
因为它是个“老好人”。Properties 继承自 Hashtable,get() 方法是父类的,签名是 Object get(Object key)。在它眼里,众生平等,不管你存的是字符串还是整数,它都原样给你吐出来。
三、 为什么会有这个坑?
你可能会问:既然 Properties 是专门用来处理字符串配置文件的,为什么 Java 还要允许我们往里面 put 整数呢?直接报错不好吗?
这就得聊聊 Java 早期的“黑历史”了。
Properties 类出现得非常早(JDK 1.0)。在那个上古时代,Java 的设计者为了少写几行代码,强行让 Properties 继承 了 Hashtable。
public class Properties extends Hashtable<Object,Object> { ... }
这就好比让一个文弱书生(Properties),直接继承了屠夫(Hashtable)的技能树。虽然书生的本职工作是写字,但他确实从他爹那里继承了“杀猪”的本事——虽然能用,但怎么看怎么别扭。
这是一个典型的“组合优于继承”原则的反面教材。如果当时采用组合模式(Properties 内部持有一个 Map),就不会暴露 put 方法给用户,也就不会有今天这个坑了。
四、 最佳实践 —— Properties 指南
既然知道坑在哪,怎么避坑就很简单了。请严格遵守以下开发规范:
1. 拉黑 put 和 get
在操作 Properties 对象时,尽量不要使用这两个从父类继承来的方法。它们是给 Hashtable 用的,不是给你用的。
2. 锁死 setProperty 和 getProperty
JDK 专门提供了类型安全的方法,请一定要用它们:
// 正确姿势
props.setProperty("timeout", "88"); // 编译器会强制要求你传入 String
// 正确读取
String val = props.getProperty("timeout"); // 此时能正确获取 "88"
3. 巧用 defaults,让默认值成为你的神助攻
源码最后一行其实还藏了个彩蛋:
((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
这允许我们构建一个“配置层级链”。比如你可以有一个 defaultProps 存放默认配置,然后创建一个 appProps 覆盖它。当主配置里没值时,它会自动去默认配置里找,非常方便。
Properties defaultProps = new Properties();
defaultProps.setProperty("theme", "light"); // 默认浅色主题
// 将 defaultProps 设为 appProps 的“父级”
Properties appProps = new Properties(defaultProps);
// appProps 里没配 theme,getProperty 会自动去 defaultProps 里找
System.out.println(appProps.getProperty("theme")); // 输出 "light"
五、 总结
下次再遇到 getProperty 返回 null,先别怀疑人生,检查一下:你是不是手滑用了 put 存了非 String 类型的数据?
| 方法 | 来源 | 特点 | 推荐指数 |
|---|---|---|---|
| get() | Hashtable | 返回 Object,来者不拒,容易埋雷 | 🚫 禁止使用 |
| getProperty() | Properties | 只返回 String,带默认值查找,类型安全 | ✅ 强烈推荐 |
既然用了 Properties,就请尊重它的“洁癖”,只喂给它 String!