为啥 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<Object,Object>
而不是 Map<String,String>
?
javadoc 声明:
因为 Properties 继承自 Hashtable,所以 put 和 putAll 方法可以应用于 Properties 对象。强烈建议不要使用它们,因为它们允许调用者插入键或值不是字符串的条目。应该改用 setProperty 方法。如果对包含非字符串键或值的“受损”属性对象调用 store 或 save 方法,则调用将失败。
既然键和值都应该是字符串,那么为什么不使用适当的泛型类型来静态地强制执行呢?
我猜想使 Properties
实现 Map<String,String>
不会完全向后兼容为 Java 5 之前编写的代码。如果您有将非字符串粘贴到 Properties 对象中的旧代码,那么该代码将不再编译Java 5。但是……这不是一件好事吗?泛型的重点不是在编译时捕获此类类型错误吗?
【问题讨论】:
【参考方案1】:因为他们在 Java 早期就匆匆忙忙地做了,并没有意识到四个版本之后会有什么影响。
从一开始就应该将泛型作为 Java 设计的一部分,但由于过于复杂且在当时没有必要,该功能被放弃了。结果,标准库中的许多代码都是在假设非泛型集合的情况下编写的。 Martin Odersky 的原型语言“Pizza”展示了它们如何在保持近乎完美的向后兼容性的同时与 Java 代码和字节码兼容。原型导致了 Java 5,其中集合类被改造为泛型,以允许旧代码继续工作。
不幸的是,如果他们追溯使 Properties
继承自 Map<String, String>
,那么以下先前有效的代码将停止工作:
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 再读一遍我的句子。我说的是他们无法修复它,因为这样做会破坏人们的代码。【参考方案2】:原本打算Properties
确实会扩展Hashtable<String,String>
。不幸的是,桥接方法的实现引起了问题。 Properties
以这种方式定义会导致 javac 生成合成方法。 Properties
应该定义一个返回 String
但需要覆盖返回 Object
的方法的 get
方法。所以增加了合成桥接法。
假设你有一门课是在糟糕的 1.4 天写的。你已经覆盖了Properties
中的一些方法。但是你没有做的是覆盖新方法。这会导致意外行为。为了避免这些桥接方法,Properties
扩展了Hashtable<Object,Object>
。同样,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<String, String>
,因为泛型是通过type erasure 以向下兼容的方式实现的,所以一旦编译器完成了它的工作,就没有泛型了。
【讨论】:
我认为 Liskov 替换原则与此无关。当你继承一个类时,Java 很高兴地让你细化泛型类型。这就是存在以上是关于为啥 java.util.Properties 实现 Map<Object,Object> 而不是 Map<String,String>的主要内容,如果未能解决你的问题,请参考以下文章