Java Map.get(Object key) 不是(完全)通用的原因是什么
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/857420/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are the reasons why Map.get(Object key) is not (fully) generic
提问by WMR
What are the reasons behind the decision to not have a fully generic get method
in the interface of java.util.Map<K, V>.
To clarify the question, the signature of the method is
为了澄清这个问题,该方法的签名是
V get(Object key)
V get(Object key)
instead of
代替
V get(K key)
V get(K key)
and I'm wondering why (same thing for remove, containsKey, containsValue).
我想知道为什么(同样的事情remove, containsKey, containsValue)。
采纳答案by newacct
As mentioned by others, the reason why get(), etc. is not generic because the key of the entry you are retrieving does not have to be the same type as the object that you pass in to get(); the specification of the method only requires that they be equal. This follows from how the equals()method takes in an Object as parameter, not just the same type as the object.
正如其他人所提到的,之所以get()等 不是通用的,因为您正在检索的条目的键不必与您传入的对象的类型相同get();该方法的规范仅要求它们相等。这源于该equals()方法如何将对象作为参数,而不仅仅是与对象的类型相同。
Although it may be commonly true that many classes have equals()defined so that its objects can only be equal to objects of its own class, there are many places in Java where this is not the case. For example, the specification for List.equals()says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So coming back to the example in this question, according to the specification of the method is possible to have a Map<ArrayList, Something>and for me to call get()with a LinkedListas argument, and it should retrieve the key which is a list with the same contents. This would not be possible if get()were generic and restricted its argument type.
尽管很多类已经equals()定义为使其对象只能等于它自己类的对象可能是普遍的事实,但在 Java 中有很多地方并非如此。例如,规范中List.equals()说如果两个 List 对象都是 List 并且具有相同的内容,则它们是相等的,即使它们是 的不同实现List。所以回到这个问题中的例子,根据方法的规范,Map<ArrayList, Something>我可以get()使用 aLinkedList作为参数调用,并且它应该检索具有相同内容的列表的键。如果get()是泛型并限制其参数类型,则这是不可能的。
回答by Anton Gogolev
Backwards compatibility, I guess. Map(or HashMap) still needs to support get(Object).
向后兼容性,我猜。Map(或HashMap)仍需支持get(Object)。
回答by Brian Agnew
The contract is expressed thus:
合同是这样表达的:
More formally, if this map contains a mapping from a key k to a value v such that (key==null ? k==null : key.equals(k)), then this method returns v; otherwise it returns null. (There can be at most one such mapping.)
更正式地说,如果此映射包含从键 k 到值 v 的映射,满足 (key==null ? k==null : key.equals(k)),则此方法返回 v;否则返回空值。(最多可以有一个这样的映射。)
(my emphasis)
(我的重点)
and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarilydependent on the class of k.
因此,成功的键查找取决于输入键对等式方法的实现。这不一定取决于 k 的类别。
回答by Jon Skeet
An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog posta while ago (admittedly in the context of Setinstead of Map). The most relevant sentence:
谷歌的一位出色的 Java 程序员 Kevin Bourrillion 不久前在一篇博客文章中写到了这个问题(不可否认,是在Set而不是的上下文中Map)。最相关的一句话:
Uniformly, methods of the Java Collections Framework (and the Google Collections Library too) never restrict the types of their parameters except when it's necessary to prevent the collection from getting broken.
一致地,Java 集合框架(以及 Google 集合库)的方法从不限制其参数的类型,除非有必要防止集合被破坏。
I'm not entirely sure I agree with it as a principle - .NET seems to be fine requiring the right key type, for example - but it's worth following the reasoning in the blog post. (Having mentioned .NET, it's worth explaining that part of the reason why it's not a problem in .NET is that there's the biggerproblem in .NET of more limited variance...)
我不完全确定我是否同意它作为一个原则 - 例如,.NET 似乎需要正确的密钥类型 - 但值得遵循博客文章中的推理。(在提到 .NET 之后,值得解释一下为什么它在 .NET 中不是问题的部分原因是 .NET 中存在更大的问题,其差异更有限......)
回答by Apocalisp
The reason is that containment is determined by equalsand hashCodewhich are methods on Objectand both take an Objectparameter. This was an early design flaw in Java's standard libraries. Coupled with limitations in Java's type system, it forces anything that relies on equals and hashCode to take Object.
原因是包含由哪些方法决定equals,hashCode哪些方法Object都采用Object参数。这是 Java 标准库中的一个早期设计缺陷。再加上 Java 类型系统的限制,它强制任何依赖于 equals 和 hashCode 的东西都采用Object.
The only way to have type-safe hash tables and equality in Java is to eschew Object.equalsand Object.hashCodeand use a generic substitute. Functional Javacomes with type classes for just this purpose: Hash<A>and Equal<A>. A wrapper for HashMap<K, V>is provided that takes Hash<K>and Equal<K>in its constructor. This class's getand containsmethods therefore take a generic argument of type K.
有在Java的类型安全哈希表和平等的唯一办法是避开Object.equals与Object.hashCode和使用一个通用的替代品。函数式 Java附带了用于此目的的类型类:Hash<A>和Equal<A>. HashMap<K, V>提供了在其构造函数中接受Hash<K>和 的包装器Equal<K>。这个类是get和contains因此采取的方法类型的通用说法K。
Example:
例子:
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
回答by Yardena
I think this section of Generics Tutorial explains the situation (my emphasis):
我认为泛型教程的这一部分解释了这种情况(我的重点):
"You need to make certain that the generic API is not unduly restrictive; it must continue to support the original contract of the API. Consider again some examples from java.util.Collection. The pre-generic API looks like:
“您需要确保泛型 API 没有过度限制;它必须继续支持 API 的原始契约。再次考虑 java.util.Collection 中的一些示例。预泛型 API 如下所示:
interface Collection {
public boolean containsAll(Collection c);
...
}
A naive attempt to generify it is:
对它进行泛化的幼稚尝试是:
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.The containsAll() method works with any kind of incoming collection. It will only succeed if the incoming collection really contains only instances of E, but:
虽然这当然是类型安全的,但它不符合 API 的原始合同。containsAll() 方法适用于任何类型的传入集合。只有当传入的集合确实只包含 E 的实例时才会成功,但是:
- The static type of the incoming collection might differ, perhaps because the caller doesn't know the precise type of the collection being passed in, or perhaps because it is a Collection<S>,where S is a subtype of E.
- It's perfectly legitimate to call containsAll() with a collection of a different type. The routine should work, returning false."
- 传入集合的静态类型可能不同,可能是因为调用者不知道传入的集合的确切类型,或者可能是因为它是 Collection<S>,其中 S 是 E 的子类型。
- 使用不同类型的集合调用 containsAll() 是完全合法的。例程应该有效,返回 false。”
回答by erickson
It's an application of Postel's Law,"be conservative in what you do, be liberal in what you accept from others."
这是波斯特尔定律的一个应用,“在你所做的事情上保持保守,在你接受别人的事情上保持自由。”
Equality checks can be performed regardless of type; the equalsmethod is defined on the Objectclass and accepts any Objectas a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept any Objecttype.
无论类型如何,都可以执行相等性检查;该equals方法在Object类上定义并接受任何Object作为参数。因此,键等价性和基于键等价性的操作可以接受任何Object类型。
When a map returns key values, it conserves as much type information as it can, by using the type parameter.
当映射返回键值时,它通过使用类型参数尽可能多地保存类型信息。
回答by Owheee
There is one more weighty reason, it can not be done technically, because it brokes Map.
还有一个更重要的原因,技术上做不到,因为它破坏了Map。
Java has polymorphic generic construction like <? extends SomeClass>. Marked such reference can point to type signed with <AnySubclassOfSomeClass>. But polymorphic generic makes that reference readonly. The compiler allows you to use generic types only as returning type of method (like simple getters), but blocks using of methods where generic type is argument (like ordinary setters).
It means if you write Map<? extends KeyType, ValueType>, the compiler does not allow you to call method get(<? extends KeyType>), and the map will be useless. The only solution is to make this method not generic: get(Object).
Java 有像<? extends SomeClass>. 标记的此类引用可以指向带有签名的类型<AnySubclassOfSomeClass>。但是多态泛型使该引用成为readonly。编译器仅允许您将泛型类型用作方法的返回类型(如简单的 getter),但会阻止使用泛型类型为参数的方法(如普通的 setter)。这意味着如果你写Map<? extends KeyType, ValueType>,编译器不允许你调用 method get(<? extends KeyType>),那么映射就没有用了。唯一的解决办法是让这个方法不是通用的:get(Object)。
回答by Stilgar
I was looking at this and thinking why they did it this way. I don't think any of the existing answers explains why they couldn't just make the new generic interface accept only the proper type for the key. The actual reason is that even though they introduced generics they did NOT create a new interface. The Map interface is the same old non-generic Map it just serves as both generic and non-generic version. This way if you have a method that accepts non-generic Map you can pass it a Map<String, Customer>and it would still work. At the same time the contract for get accepts Object so the new interface should support this contract too.
我看着这个并思考他们为什么这样做。我认为现有的任何答案都不能解释为什么他们不能让新的通用接口只接受正确的键类型。真正的原因是,即使他们引入了泛型,他们也没有创建新的接口。Map 接口与旧的非泛型 Map 相同,它仅用作泛型和非泛型版本。这样,如果您有一个接受非泛型 Map 的方法,您可以将它传递给 aMap<String, Customer>并且它仍然可以工作。同时 get 的契约接受 Object 所以新的接口也应该支持这个契约。
In my opinion they should have added a new interface and implemented both on existing collection but they decided in favor of compatible interfaces even if it means worse design for the get method. Note that the collections themselves would be compatible with existing methods only the interfaces wouldn't.
在我看来,他们应该添加一个新接口并在现有集合上实现这两个接口,但他们决定支持兼容接口,即使这意味着 get 方法的设计更糟。请注意,集合本身将与现有方法兼容,只有接口不兼容。
回答by henva
We are doing big refactoring just now and we were missing this strongly typed get() to check that we did not missed some get() with old type.
我们刚刚进行了大的重构,我们错过了这个强类型的 get() 来检查我们没有错过一些旧类型的 get() 。
But I found workaround/ugly trick for compilation time check: create Map interface with strongly typed get, containsKey, remove... and put it to java.util package of your project.
但是我发现了编译时间检查的解决方法/丑陋的技巧:创建具有强类型 get、containsKey、remove... 的 Map 接口,并将其放入项目的 java.util 包中。
You will get compilation errors just for calling get(), ... with wrong types, everything others seems ok for compiler (at least inside eclipse kepler).
你会因为调用 get(), ... 类型错误而得到编译错误,其他的一切对于编译器来说似乎没问题(至少在 eclipse kepler 中)。
Do not forget to delete this interface after check of your build as this is not what you want in runtime.
在检查您的构建后不要忘记删除此接口,因为这不是您在运行时想要的。

