Map.get(Object key)不是(完全)generics的原因是什么?
决定在java.util.Map<K, V>
的接口中没有完全通用的get方法的原因是什么?
为了澄清这个问题,该方法的签名是
V get(Object key)
代替
V get(K key)
我想知道为什么( remove, containsKey, containsValue
相同的东西remove, containsKey, containsValue
)。
正如其他人所提到的, get()
等原因不是通用的,因为您正在检索的条目的键不必与您传递给get()
的对象的types相同。 该方法的规格只要求它们相等。 这是从equals()
方法如何将一个Object作为参数,而不是与该对象相同的types。
虽然通常情况下许多类都定义了equals()
所以它的对象只能和它自己的类的对象相同,Java中有很多地方不是这样。 例如, List.equals()
的规范说,如果两个List对象都是List并且具有相同的内容,即使它们是List
不同实现,也是相等的。 所以回到这个问题的例子中,根据该方法的规范可能有一个Map<ArrayList, Something>
并且用一个LinkedList
作为参数调用get()
,并且它应该检索一个内容相同的列表。 这是不可能的,如果get()
是generics的并且限制了它的参数types。
Google的一位出色的Java编码人员Kevin Bourrillion刚才在一篇博客文章中写到了这个问题(公认的是Set
而不是Map
)。 最相关的一句话:
一般而言,Java集合框架(以及Google集合库)的方法不会限制其参数的types,除非有必要防止集合被破坏。
我不完全确定我是否同意这个原则 – 例如,.NET似乎是正确的,需要正确的键types,但值得在博客文章中加以推理。 (在提到.NET的时候,值得说明的是,在.NET中没有问题的部分原因是.NET中的更大的问题是更有限的方差…)
合同表示如下:
更正式地说,如果这个映射包含从键k到值v的映射(key == null?k == null: key.equals(k) ),那么这个方法返回v; 否则返回null。 (最多可以有一个这样的映射。)
(我的重点)
因此,成功的密钥查找取决于input密钥的等式方法的实现。 这不一定取决于k的类别。
这是波斯特尔法则的一个应用, “在你做的事情上要保守,在你从别人那里接受自由的事情”。
平等检查可以不分types地进行; equals
方法在Object
类上定义并接受任何Object
作为参数。 所以,对于关键等价和基于关键等价的操作来说,接受任何Object
types是有意义的。
当映射返回键值时,通过使用types参数,可以尽可能多地保存types信息。
我认为这部分的generics教程解释的情况(我的重点):
“你需要确定genericsAPI不是过分的限制,它必须继续支持API的原始契约,再次考虑java.util.Collection中的一些例子。
interface Collection { public boolean containsAll(Collection c); ... }
一个天真的尝试生成它是:
interface Collection<E> { public boolean containsAll(Collection<E> c); ... }
虽然这确实是安全的,但它不符合API的原始合同。 containsAll()方法适用于任何types的传入集合。 只有传入的集合确实只包含E的实例,它才会成功,但是:
- 传入集合的静态types可能不同,也许是因为调用者不知道传入的集合的确切types,或者可能是因为它是一个Collection <S>,其中S是E的子types。
- 使用不同types的集合调用containsAll()是完全合法的。 例程应该工作,返回错误。“
原因是遏制是由equals
和hashCode
决定的,它们是Object
上的方法,都是Object
参数。 这是Java标准库中的一个早期devise缺陷。 再加上Javatypes系统的限制,它强制依赖于equals和hashCode的任何东西都取Object
。
在Java中使用types安全散列表和相等的唯一方法是避开Object.equals
和Object.hashCode
并使用通用的替代方法。 function性Java自带types类: Hash<A>
和Equal<A>
。 提供HashMap<K, V>
包装器,它在构造函数中使用Hash<K>
和Equal<K>
。 这个类的get
和contains
方法因此采用K
types的generics参数。
例:
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
还有一个重要的原因,它不能在技术上做,因为它打破了地图。
Java具有多态generics结构,如<? extends SomeClass>
<? extends SomeClass>
。 标记的这种引用可以指向使用<AnySubclassOfSomeClass>
签名的types。 但是多态generics只能使用这个引用。 编译器允许你只使用genericstypes作为返回types的方法(如简单的getter),但是使用genericstypes为参数的方法(像普通setters)。 这意味着如果你写Map<? extends KeyType, ValueType>
Map<? extends KeyType, ValueType>
,编译器不允许你调用方法get(<? extends KeyType>)
,并且映射将是无用的。 唯一的解决办法是使这个方法不通用: get(Object)
。
兼容性。
在generics可用之前,刚刚获得(Object o)。
如果他们改变这个方法来得到(<K> o),它可能会迫使大量的代码维护到Java用户只是为了使工作代码再次编译。
他们可能已经引入了一个额外的方法,比如说get_checked(<K> o),并且弃用旧的get()方法,所以有一个更温和的转换path。 但由于某种原因,这还没有完成。 (我们现在的情况是,你需要安装像findBugs这样的工具来检查get()参数和声明的键值types<K>之间的types兼容性。)
有关.equals()的语义的争论是假的,我想。 (从技术上说,他们是正确的,但我仍然认为他们是假的,如果没有任何共同的超类,任何devise师都不会使他的想法成为正确的(o2)。)
向后兼容性,我猜。 Map
(或HashMap
)仍然需要支持get(Object)
。
我正在看这个,思考他们为什么这样做。 我不认为任何现有的答案解释了为什么他们不能让新的通用接口只接受适当的密钥types。 实际的原因是,即使他们引入generics,他们并没有创build一个新的接口。 Map接口与旧的非generics地图只是generics和非generics版本。 这样,如果你有一个接受非genericsMap的方法,你可以传递一个Map<String, Customer>
,它仍然可以工作。 同时get的合同接受Object,所以新的接口也应该支持这个合同。
在我看来,他们应该添加一个新的接口,并在现有的集合上实现,但是他们决定采用兼容的接口,即使这意味着get方法的devise更糟糕。 请注意,集合本身将与现有方法兼容,只有接口不会。
我们现在正在做大的重构,而且我们遗漏了这个强types的get()来检查我们没有遗漏一些get()与旧types。
但是,我发现解决方法/丑陋的编译时间检查:使用强types的get,containsKey,remove …创buildMap接口,并将其放到您的项目的java.util包中。
你会得到编译错误只是为了调用get(),…错误的types,其他所有的东西似乎对编译器来说是好的(至less在eclipse kepler里面)。
在检查完构build之后,不要忘记删除这个接口,因为这不是你想要的。