为什么java.util.Properties实现了Map<Object,Object>而不是Map<String,String>?

55

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

那么,为什么在Java 5中要改装这个类来实现Map<Object,Object>而不是Map<String,String>呢?

javadoc中提到:

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

既然键和值都应该是字符串,那么为什么不通过正确的泛型类型静态地强制执行呢?

我想,让Properties实现Map<String,String>可能不完全向后兼容于为Java 5编写的代码。如果您有将非字符串插入Properties对象的旧代码,那么该代码将无法在Java 5中编译。但是... 那不是一件好事吗?泛型的整个目的不就是在编译时捕捉此类类型错误吗?

5个回答

55
因为在 Java 初期,他们匆忙地做出了这个决定,并没有意识到四个版本之后会产生什么影响。
泛型本应从 Java 设计之初便是其中一部分,但因为它太过复杂,在当时被认为是不必要的而被放弃。结果导致许多标准库中的代码都是用于非泛型集合的假设编写的。直到 Martin Odersky 的原型语言 "Pizza" 出现,它展示了如何在保持近乎完美的向后兼容性的同时实现泛型。这个原型最终导致了 Java 5,其中的集合类被更新以支持泛型,且旧代码仍能正常工作。
不幸的是,如果他们要让 Properties 继承自 Map<String, String>,那么以下以前有效的代码就会停止工作:
Map<Object, Object> x = new Properties()
x.put("flag", true)

我不明白为什么会有人这样做,但是Sun公司对Java的向后兼容承诺已经超越了英雄主义并变得毫无意义。

现在被大多数受过教育的观察者所认可的是,Properties根本不应该继承自Map。它应该包裹Map,只公开那些有意义的Map功能。

Martin Odersky在重新发明Java之后,创造了新的Scala语言,它更简洁,继承的错误更少,并在许多领域开创了新的局面。如果你觉得Java的小问题很烦人,可以看看它。


1
实际上,Map x = new Properties() 可以两者兼容。它是人们将 properties.put("flag", Boolean.TRUE); 这样的内容放入属性对象中。我见过人们将各种数据放入 Properties 对象中,但从未见过非字符串键。 ;) - Peter Lawrey
6
等一下!在Java5之前,Map<Object,Object> x = new Properties() 是不合法的语法,因为 <Object,Object> 这种语法是在Java5中引入的。因此,“以前有效的代码”这个说法是不正确的。 - Mike Kucera
1
请再读一遍我的句子。我说的是他们无法修复它,因为这样做会破坏人们的代码。 - Marcus Downing

27

最初的设想是Properties确实会扩展Hashtable<String,String>。不幸的是,桥接方法的实现导致了问题。这种方式定义的Properties将导致javac生成合成方法。例如,Properties应该定义一个返回Stringget方法,但需要覆盖一个返回Object的方法。因此,会添加一个合成的桥接方法。

假设您有一个在1.4时代编写的类。您已经重写了一些Properties中的方法。但您没有重写新方法。这会导致意外行为。为避免这些桥接方法,Properties扩展了Hashtable<Object,Object>。同样地,Iterable不返回(只读的)SimpleIterable,因为那会向Collection实现添加方法。


13

一行代码(如果不需要警告则为两行)来从Properties创建Map:

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

没有回答问题,但是回答了问题最初的原因。直截了当。 - Richard Gomes
2
这里需要使用 "rawtypes" 吗?(至少在我的 IntelliJ IDEA 中,使用 "unchecked" 就足以抑制任何警告。) - Jonik

4

向后兼容性。


2
原因:Liskov替换原则和向后兼容性。 Properties扩展了Hashtable,因此必须接受Hashtable接受的所有消息,这意味着接受put(Object,Object)。它必须扩展普通的Hashtable而不是Hashtable<String,String>,因为泛型是通过类型擦除以向下兼容的方式实现的,所以一旦编译器完成其工作,就没有泛型了。

3
我认为Liskov替换原则与此无关。在继承类时,Java可以愉快地让你改进泛型类型。这就是为什么存在<T extends Sometype>和 <T super Sometype>语法的原因。 - Mike Kucera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接