首页 文章

Map.get(Object key)不是(完全)泛型的原因是什么

提问于
浏览
375

决定在java.util.Map<K, V>的接口中没有完全通用的get方法的原因是什么 .

为了澄清这个问题,该方法的签名是

V get(Object key)

代替

V get(K key)

我想知道为什么(同样的事情 remove, containsKey, containsValue ) .

11 回答

  • 2

    这是Postel's Law, "be conservative in what you do, be liberal in what you accept from others."的应用

    无论何种类型,都可以进行平等检查; equals 方法在 Object 类上定义,并接受任何 Object 作为参数 . 因此,关键等价和基于键等价的操作接受任何 Object 类型是有意义的 .

    当映射返回键值时,通过使用type参数,它可以保留尽可能多的类型信息 .

  • 1

    原因是包含由 equalshashCode 确定,它们是 Object 上的方法,并且都采用 Object 参数 . 这是Java 's standard libraries. Coupled with limitations in Java'类型系统中的早期设计缺陷,它强制依赖于equals和hashCode的任何东西都采用 Object .

    在Java中使用类型安全哈希表和相等的唯一方法是避开 Object.equalsObject.hashCode 并使用泛型替代 . Functional Java仅为此目的提供类型类:Hash<A>Equal<A> . 提供了HashMap<K, V>的包装器,其构造函数中包含 Hash<K>Equal<K> . 因此,此类的 getcontains 方法采用类型为 K 的泛型参数 .

    例:

    HashMap<String, Integer> h =
      new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);
    
    h.add("one", 1);
    
    h.get("one"); // All good
    
    h.get(Integer.valueOf(1)); // Compiler error
    
  • 28

    我认为Generics Tutorial的这一部分解释了这种情况(我的重点):

    “您需要确保通用API不会过度限制;它必须继续支持API的原始 Contract . 再次考虑java.util.Collection中的一些示例 . 预通用API如下所示:

    interface Collection { 
      public boolean containsAll(Collection c);
      ...
    }
    

    一个天真的尝试,它是:

    interface Collection<E> { 
      public boolean containsAll(Collection<E> c);
      ...
    }
    

    While this is certainly type safe, it doesn’t live up to the API’s original contract. containsAll()方法适用于任何类型的传入集合 . 只有当传入的集合实际上只包含E的实例时,它才会成功,但是:

    • 传入集合的静态类型可能不同,可能是因为调用者不知道传入的集合的精确类型,或者可能是因为它是Collection <S>,其中S是E的子类型 .

    • 使用不同类型的集合调用containsAll()是完全合法的 . 例程应该有效,返回错误 . “

  • 101

    Contract 表达如下:

    更正式地说,如果此映射包含从键k到值v的映射,使得(key == null?k == null:key.equals(k)),则此方法返回v;否则返回null . (最多可以有一个这样的映射 . )

    (我的重点)

    因此,成功的键查找取决于输入键的相等方法的实现 . 这不一定取决于k的类别 .

  • 4

    我在看这个,并想着他们为什么这样做 . 我不要只是让新的通用接口只接受密钥的正确类型 . 实际的原因是,即使他们引入了泛型,他们也没有创建新的界面 . Map接口与旧的非泛型Map相同,它只用作通用和非泛型版本 . 这样,如果你有一个接受非泛型Map的方法,你可以传递一个 Map<String, Customer> ,它仍然可以工作 . 同时get的 Contract 接受Object,所以新的接口也应该支持这个 Contract .

    在我看来,他们应该添加一个新的接口,并在现有的集合上实现,但他们决定支持兼容的接口,即使这意味着更糟糕的get方法设计 . 请注意,集合本身将与现有方法兼容,只有接口不兼容 .

  • 11

    我认为向后兼容性 . Map (或 HashMap )仍然需要支持 get(Object) .

  • 0

    谷歌的一位杰出的Java程序员Kevin Bourrillion在一段时间之前在_352456中写到了这个问题(当然是在 Set 而不是 Map 的背景下) . 最相关的句子:

    统一地,Java Collections Framework(以及Google Collections Library)的方法从不限制其参数的类型,除非必须防止集合被破坏 .

    我认为值得遵循博客文章中的推理 . (提到.NET之后,它在.NET中不是一个问题,因为.NET中存在更大的问题,因为方差更加有限......)

  • 5

    还有一个重要的原因,它不能在技术上完成,因为它破坏了Map .

    Java具有多态通用结构,如 <? extends SomeClass> . 标记为此类引用可以指向使用 <AnySubclassOfSomeClass> 签名的类型 . 但是多态通用使得引用 readonly . 编译器允许您仅将泛型类型用作返回类型的方法(如简单的getter),但是使用泛型类型为参数的方法(如普通的setter) . 这意味着如果您编写 Map<? extends KeyType, ValueType> ,则编译器不会允许你调用方法 get(<? extends KeyType>) , Map 将无用 . 唯一的解决方案是使此方法不通用: get(Object) .

  • 16

    我们刚刚进行了大量的重构,我们错过了这个强类型的get()来检查我们是不是错过了一些旧类型的get() .

    但是我找到了编译时间检查的解决方法/丑陋技巧:使用强类型get,containsKey,remove ...创建Map接口,并将其放到项目的java.util包中 .

    你会得到编译错误只是为了调用get(),...有错误的类型,其他一切似乎都适合编译器(至少在eclipse kepler中) .

    在检查构建之后不要忘记删除此接口,因为这不是您在运行时所需的 .

  • 245

    正如其他人所提到的, get() 等不是通用的原因是因为您要检索的条目的键不必与传入 get() 的对象的类型相同;方法的规范只要求它们相等 . 这取决于 equals() 方法如何将Object作为参数,而不仅仅是与对象相同的类型 .

    尽管通常可以确定许多类已定义 equals() ,以使其对象只能等于其自己的类的对象,但Java中有许多地方不是这种情况 . 例如, List.equals() 的规范表明如果两个List对象都是Lists并具有相同的内容,则它们是相等的,即使它们是 List 的不同实现 . 所以回到这个问题的例子,根据方法的规范可以有 Map<ArrayList, Something> 并且我可以用 LinkedList 作为参数调用 get() ,它应该检索具有相同内容的列表的键 . 如果 get() 是通用的并限制其参数类型,则这是不可能的 .

  • 1

    兼容性 .

    在提供泛型之前,只有get(Object o) .

    如果他们改变了这个方法来获得(<K> o)它可能会迫使大量代码维护到java用户只是为了再次编译工作代码 .

    他们本可以引入一个额外的方法,比如get_checked(<K> o)并弃用旧的get()方法,以便有一个更温和的过渡路径 . 但由于某种原因,这没有做到 . (我们现在的情况是你需要安装像findBugs这样的工具来检查get()参数和 Map 声明的密钥类型<K>之间的类型兼容性 . )

    我认为,与.equals()的语义有关的论点是假的 . (从技术上讲,他们是正确的,但我仍然认为他们是假的 . 如果o1和o2没有任何共同的超类,那么他的正确思想中的设计师永远不会使o1.equals(o2)成立 . )

相关问题