为啥 java.util.Properties 实现 Map<Object,Object> 而不是 Map<String,String>

Posted

技术标签:

【中文标题】为啥 java.util.Properties 实现 Map<Object,Object> 而不是 Map<String,String>【英文标题】:Why does java.util.Properties implement Map<Object,Object> and not Map<String,String>为什么 java.util.Properties 实现 Map<Object,Object> 而不是 Map<String,String> 【发布时间】:2010-10-26 18:55:09 【问题描述】:

java.util.Properties 类用于表示键和值都是字符串的映射。这是因为Properties 对象用于读取.properties 文件,这些文件是文本文件。

那么,为什么在 Java 5 中他们改造这个类来实现 Map&lt;Object,Object&gt; 而不是 Map&lt;String,String&gt;

javadoc 声明:

因为 Properties 继承自 Hashtable,所以 put 和 putAll 方法可以应用于 Properties 对象。强烈建议不要使用它们,因为它们允许调用者插入键或值不是字符串的条目。应该改用 setProperty 方法。如果对包含非字符串键或值的“受损”属性对象调用 store 或 save 方法,则调用将失败。

既然键和值都应该是字符串,那么为什么不使用适当的泛型类型来静态地强制执行呢?

我猜想使 Properties 实现 Map&lt;String,String&gt; 不会完全向后兼容为 Java 5 之前编写的代码。如果您有将非字符串粘贴到 Properties 对象中的旧代码,那么该代码将不再编译Java 5。但是……这不是一件好事吗?泛型的重点不是在编译时捕获此类类型错误吗?

【问题讨论】:

【参考方案1】:

因为他们在 Java 早期就匆匆忙忙地做了,并没有意识到四个版本之后会有什么影响。

从一开始就应该将泛型作为 Java 设计的一部分,但由于过于复杂且在当时没有必要,该功能被放弃了。结果,标准库中的许多代码都是在假设非泛型集合的情况下编写的。 Martin Odersky 的原型语言“Pizza”展示了它们如何在保持近乎完美的向后兼容性的同时与 Java 代码和字节码兼容。原型导致了 Java 5,其中集合类被改造为泛型,以允许旧代码继续工作。

不幸的是,如果他们追溯使 Properties 继承自 Map&lt;String, String&gt;,那么以下先前有效的代码将停止工作:

Map<Object, Object> x = new Properties()
x.put("flag", true)

为什么有人会这样做,我无法理解,但 Sun 对 Java 向后兼容性的承诺已经超越了英雄主义,变得毫无意义。

现在大多数受过教育的观察者都认为Properties 根本不应该继承自Map。相反,它应该环绕 Map,只显示 Map 中有意义的那些功能。

自重新发明 Java 以来,Martin Odersky 继续创建新的 Scala 语言,该语言更简洁,继承的错误更少,并在许多领域开辟了新天地。如果你觉得 Java 的琐事很烦人,那就看看吧。

【讨论】:

实际上 Map x = new Properties() 可以两种方式工作。它的人把 properties.put("flag", Boolean.TRUE);我见过人们将各种数据放入一个 Properties 对象中,但从来没有一个非字符串键。 ;) 等一下! Map x = new Properties() 在 Java 5 之前是不合法的, 语法是在 Java 5 中引入的。所以“以前有效的代码”的说法是不正确的。 再读一遍我的句子。我说的是他们无法修复它,因为这样做会破坏人们的代码。【参考方案2】:

原本打算Properties 确实会扩展Hashtable&lt;String,String&gt;。不幸的是,桥接方法的实现引起了问题。 Properties 以这种方式定义会导致 javac 生成合成方法。 Properties 应该定义一个返回 String 但需要覆盖返回 Object 的方法的 get 方法。所以增加了合成桥接法。

假设你有一门课是在糟糕的 1.4 天写的。你已经覆盖了Properties 中的一些方法。但是你没有做的是覆盖新方法。这会导致意外行为。为了避免这些桥接方法,Properties 扩展了Hashtable&lt;Object,Object&gt;。同样,Iterable 不会返回(只读)SimpleIterable,因为这会为Collection 实现添加方法。

【讨论】:

【参考方案3】:

从属性创建地图的单行(无警告的两行):

@SuppressWarnings( "unchecked", "rawtypes" )
Map<String, String> sysProps = new HashMap(System.getProperties());

【讨论】:

不回答问题。但确实首先回答了导致这个问题的原因。直奔主题。 这里需要“rawtypes”吗? (至少在我的 IntelliJ IDEA 中,“未选中”足以抑制任何警告。)【参考方案4】:

向后兼容性。

【讨论】:

【参考方案5】:

原因:Liskov substitution principle 和向后兼容性。 Properties 扩展 Hashtable,因此必须接受 Hashtable 将接受的所有消息 - 这意味着接受 put(Object, Object)。而且它必须扩展普通的Hashtable 而不是Hashtable&lt;String, String&gt;,因为泛型是通过type erasure 以向下兼容的方式实现的,所以一旦编译器完成了它的工作,就没有泛型了。

【讨论】:

我认为 Liskov 替换原则与此无关。当你继承一个类时,Java 很高兴地让你细化泛型类型。这就是存在 语法的原因。

以上是关于为啥 java.util.Properties 实现 Map<Object,Object> 而不是 Map<String,String>的主要内容,如果未能解决你的问题,请参考以下文章

java.util.Properties类

java.util.Properties

Java.util.Properties

java.util.Properties 是不是支持嵌套属性?

java.util.properties

你能推荐一些比 java.util.Properties 更高级的东西吗?