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

时间:2009-05-13 11:28:31

标签: java generics collections map

决定不使用完全通用的get方法的原因是什么 在java.util.Map<K, V>

的界面中

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

V get(Object key)

而不是

V get(K key)

我想知道为什么(remove, containsKey, containsValue同样如此)。

11 个答案:

答案 0 :(得分:253)

正如其他人所提到的,get()等不是通用的原因是因为您要检索的条目的键不必与传递给{{1的对象的类型相同}};方法的规范只要求它们相等。这是根据get()方法如何将Object作为参数,而不仅仅是与对象相同的类型。

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

答案 1 :(得分:103)

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

  

统一,Java的方法   收藏框架(和谷歌   收藏图书馆也没有   限制其参数的类型   除非有必要预防   收集破碎。

我不完全确定我同意它作为一个原则 - 例如,.NET似乎很好,需要正确的密钥类型 - 但值得遵循博客文章中的推理。 (提到.NET之后,值得解释的是,在.NET中不存在问题的部分原因是.NET中存在更大问题的方差更为有限......)

答案 2 :(得分:29)

合同表达如下:

  

更正式地说,如果这个地图包含一个   从密钥k映射到值v等   那(key == null?k == null:    key.equals(k)),然后是这个方法   返回v;否则返回null。   (最多可以有一个这样的   映射。)

(我的重点)

因此,成功的键查找取决于输入键的相等方法的实现。那不是必然依赖于k的类。

答案 3 :(得分:17)

这是Postel's Law,的一个应用“在你所做的事情上保守一点,在你接受别人的事上保持自由。”

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

当地图返回键值时,它会使用type参数保存尽可能多的类型信息。

答案 4 :(得分:11)

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

“您需要确保通用API不会过度限制;它必须 继续支持API的原始合同。再考虑一些例子 来自java.util.Collection。预通用API看起来像:

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

一种天真的尝试,即:

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

虽然这当然是类型安全的,但它不符合API的原始合同。 containsAll()方法适用于任何类型的传入集合。它只会 如果传入集合实际上只包含E的实例,则成功,但是:

  • 传入的静态类型 收藏可能会有所不同 因为来电者不知道 精确的集合类型 传入,或者也许因为它是一个 集合&lt; S&gt ;,其中S是a E.的子类型。
  • 这很完美 合法调用containsAll() 一个不同类型的集合。该 例程应该有效,返回假。“

答案 5 :(得分:6)

原因是遏制由equalshashCode决定,这些方法是Object上的方法,两者都采用Object参数。这是Java标准库中的早期设计缺陷。再加上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

答案 6 :(得分:4)

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

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

答案 7 :(得分:2)

兼容性。

在提供泛型之前,只有get(对象o)。

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

他们可以引入附加方法,比如get_checked(&lt; K&gt; o)并弃用旧的get()方法,因此有一个更温和的过渡路径。但出于某种原因,这还没有完成。 (我们现在的情况是你需要安装像findBugs这样的工具来检查get()参数和地图的声明密钥类型&lt; K&gt;之间的类型兼容性。)

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

答案 8 :(得分:1)

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

答案 9 :(得分:1)

我正在看着这个,并想着他们为什么这样做。我不认为任何现有的答案都解释了为什么他们不能只让新的通用接口只接受密钥的正确类型。实际的原因是,即使他们引入了泛型,他们也没有创建新的界面。 Map接口与旧的非泛型Map相同,它只用作通用和非泛型版本。这样,如果您有一个接受非通用Map的方法,您可以将它传递给Map<String, Customer>,它仍然有效。同时get的契约接受Object,所以新的界面也应该支持这个契约。

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

答案 10 :(得分:0)

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

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

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

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