【问题标题】: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。但是……这不是一件好事吗?泛型的重点不是在编译时捕获此类类型错误吗?

【问题讨论】:

    标签: java generics collections


    【解决方案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 很高兴地让你细化泛型类型。这就是存在 语法的原因。
        猜你喜欢
        • 2018-10-29
        • 1970-01-01
        • 1970-01-01
        • 2018-09-22
        • 1970-01-01
        • 1970-01-01
        • 2013-05-24
        • 2014-01-29
        • 2020-08-01
        相关资源
        最近更新 更多