What are the reasons behind the decision to not have a fully generic get method in the interface of java.util.Map<K, V>
.
决定在java.util.Map
To clarify the question, the signature of the method is
为了澄清这个问题,该方法的签名是
V get(Object key)
V get(对象键)
instead of
V get(K key)
V get(K键)
and I'm wondering why (same thing for remove, containsKey, containsValue
).
我想知道为什么(删除相同的东西,containsKey,containsValue)。
11 个解决方案
#1
243
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()方法如何将Object作为参数,而不仅仅是与对象相同的类型。
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 LinkedList
as 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对象都是Lists并具有相同的内容,则它们是相等的,即使它们是List的不同实现。所以回到这个问题的例子,根据方法的规范可以有一个Map
#2
101
An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog post a while ago (admittedly in the context of Set
instead 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 Collections Framework(以及Google Collections Library)的方法从不限制其参数的类型,除非必须防止集合被破坏。
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 bigger problem in .NET of more limited variance...)
我并不完全确定我同意它作为一个原则 - 例如,.NET似乎很好,需要正确的密钥类型 - 但它值得遵循博客文章中的推理。 (提到.NET之后,值得解释一下,在.NET中不存在问题的一部分原因是.NET中存在更大限制的方差问题......)
#3
27
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;否则返回null。 (最多可以有一个这样的映射。)
(my emphasis)
and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarily dependent on the class of k.
因此,成功的键查找取决于输入键的相等方法的实现。这不一定取决于k的类别。
#4
16
It's an application of Postel's Law, "be conservative in what you do, be liberal in what you accept from others."
这是Postel定律的一个应用,“在你所做的事情上要保守,在你接受别人的事情上保持*。”
Equality checks can be performed regardless of type; the equals
method is defined on the Object
class and accepts any Object
as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept any Object
type.
无论何种类型,都可以进行平等检查; equals方法在Object类上定义,并接受任何Object作为参数。因此,关键等价和基于键等价的操作接受任何Object类型都是有意义的。
When a map returns key values, it conserves as much type information as it can, by using the type parameter.
当映射返回键值时,通过使用type参数,它可以保留尽可能多的类型信息。
#5
11
I think this section of Generics Tutorial explains the situation (my emphasis):
我认为Generics Tutorial的这一部分解释了这种情况(我的重点):
"You need to make certain that the generic API is not unduly restrictive; it mustcontinue to support the original contract of the API. Consider again some examplesfrom 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 onlysucceed if the incoming collection really contains only instances of E, but:
虽然这肯定是类型安全的,但它不符合API的原始合同.containsAll()方法适用于任何类型的传入集合。如果传入的集合实际上只包含E的实例,它将只会被激活,但是:
- The static type of the incomingcollection might differ, perhapsbecause the caller doesn’t know theprecise type of the collection beingpassed in, or perhaps because it is aCollection<S>,where S is asubtype of E.
- It’s perfectlylegitimate to call containsAll() witha collection of a different type. Theroutine should work, returning false."
incomingcollection的静态类型可能不同,可能是因为调用者不知道被占用的集合的精确类型,或者可能是因为它是aCollection ,其中S是E的子类型。
使用不同类型的集合调用containsAll()是完全合法的。该例程应该有效,返回错误。“
#6
5
The reason is that containment is determined by equals
and hashCode
which are methods on Object
and both take an Object
parameter. 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.equals
and Object.hashCode
and use a generic substitute. Functional Java comes 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 get
and contains
methods therefore take a generic argument of type K
.
在Java中使用类型安全的哈希表和相等的唯一方法是避免使用Object.equals和Object.hashCode并使用泛型替换。功能Java为此提供了类型类:Hash 和Equal 。提供了HashMap
Example:
HashMap<String, Integer> h = new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);h.add("one", 1);h.get("one"); // All goodh.get(Integer.valueOf(1)); // Compiler error
#7
4
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有多态通用结构,如
。标记为此类引用可以指向使用
#8
1
Backwards compatibility, I guess. Map
(or HashMap
) still needs to support get(Object)
.
我认为向后兼容性。 Map(或HashMap)仍然需要支持get(Object)。
#9
1
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的方法,你可以传递一个Map
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方法设计。请注意,集合本身将与现有方法兼容,只有接口不兼容。
#10
1
Compatibility.
Before generics were available, there was just get(Object o).
在提供泛型之前,只有get(Object o)。
Had they changed this method to get(<K> o) it would have potentially forced massive code maintenance onto java users just to make working code compile again.
如果他们改变了这个方法来获得(
They could have introduced an additional method, say get_checked(<K> o) and deprecate the old get() method so there was a gentler transition path. But for some reason, this was not done. (The situation we are in now is that you need to install tools like findBugs to check for type compatibility between the get() argument and the declared key type <K> of the map.)
他们本可以引入一个额外的方法,比如get_checked(
The arguments relating to the semantics of .equals() are bogus, I think. (Technically they're correct, but I still think they're bogus. No designer in his right mind is ever going to make o1.equals(o2) true if o1 and o2 do not have any common superclass.)
我认为,与.equals()的语义有关的论点是假的。 (从技术上讲,他们是正确的,但我仍然认为他们是假的。如果o1和o2没有任何共同的超类,那么他的正确思想中的设计师永远不会使o1.equals(o2)成立。)
#11
0
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.
在检查构建之后不要忘记删除此接口,因为这不是您在运行时所需的。
#1
243
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()方法如何将Object作为参数,而不仅仅是与对象相同的类型。
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 LinkedList
as 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对象都是Lists并具有相同的内容,则它们是相等的,即使它们是List的不同实现。所以回到这个问题的例子,根据方法的规范可以有一个Map
#2
101
An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog post a while ago (admittedly in the context of Set
instead 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 Collections Framework(以及Google Collections Library)的方法从不限制其参数的类型,除非必须防止集合被破坏。
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 bigger problem in .NET of more limited variance...)
我并不完全确定我同意它作为一个原则 - 例如,.NET似乎很好,需要正确的密钥类型 - 但它值得遵循博客文章中的推理。 (提到.NET之后,值得解释一下,在.NET中不存在问题的一部分原因是.NET中存在更大限制的方差问题......)
#3
27
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;否则返回null。 (最多可以有一个这样的映射。)
(my emphasis)
and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarily dependent on the class of k.
因此,成功的键查找取决于输入键的相等方法的实现。这不一定取决于k的类别。
#4
16
It's an application of Postel's Law, "be conservative in what you do, be liberal in what you accept from others."
这是Postel定律的一个应用,“在你所做的事情上要保守,在你接受别人的事情上保持*。”
Equality checks can be performed regardless of type; the equals
method is defined on the Object
class and accepts any Object
as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept any Object
type.
无论何种类型,都可以进行平等检查; equals方法在Object类上定义,并接受任何Object作为参数。因此,关键等价和基于键等价的操作接受任何Object类型都是有意义的。
When a map returns key values, it conserves as much type information as it can, by using the type parameter.
当映射返回键值时,通过使用type参数,它可以保留尽可能多的类型信息。
#5
11
I think this section of Generics Tutorial explains the situation (my emphasis):
我认为Generics Tutorial的这一部分解释了这种情况(我的重点):
"You need to make certain that the generic API is not unduly restrictive; it mustcontinue to support the original contract of the API. Consider again some examplesfrom 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 onlysucceed if the incoming collection really contains only instances of E, but:
虽然这肯定是类型安全的,但它不符合API的原始合同.containsAll()方法适用于任何类型的传入集合。如果传入的集合实际上只包含E的实例,它将只会被激活,但是:
- The static type of the incomingcollection might differ, perhapsbecause the caller doesn’t know theprecise type of the collection beingpassed in, or perhaps because it is aCollection<S>,where S is asubtype of E.
- It’s perfectlylegitimate to call containsAll() witha collection of a different type. Theroutine should work, returning false."
incomingcollection的静态类型可能不同,可能是因为调用者不知道被占用的集合的精确类型,或者可能是因为它是aCollection ,其中S是E的子类型。
使用不同类型的集合调用containsAll()是完全合法的。该例程应该有效,返回错误。“
#6
5
The reason is that containment is determined by equals
and hashCode
which are methods on Object
and both take an Object
parameter. 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.equals
and Object.hashCode
and use a generic substitute. Functional Java comes 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 get
and contains
methods therefore take a generic argument of type K
.
在Java中使用类型安全的哈希表和相等的唯一方法是避免使用Object.equals和Object.hashCode并使用泛型替换。功能Java为此提供了类型类:Hash 和Equal 。提供了HashMap
Example:
HashMap<String, Integer> h = new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);h.add("one", 1);h.get("one"); // All goodh.get(Integer.valueOf(1)); // Compiler error
#7
4
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有多态通用结构,如
。标记为此类引用可以指向使用
#8
1
Backwards compatibility, I guess. Map
(or HashMap
) still needs to support get(Object)
.
我认为向后兼容性。 Map(或HashMap)仍然需要支持get(Object)。
#9
1
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的方法,你可以传递一个Map
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方法设计。请注意,集合本身将与现有方法兼容,只有接口不兼容。
#10
1
Compatibility.
Before generics were available, there was just get(Object o).
在提供泛型之前,只有get(Object o)。
Had they changed this method to get(<K> o) it would have potentially forced massive code maintenance onto java users just to make working code compile again.
如果他们改变了这个方法来获得(
They could have introduced an additional method, say get_checked(<K> o) and deprecate the old get() method so there was a gentler transition path. But for some reason, this was not done. (The situation we are in now is that you need to install tools like findBugs to check for type compatibility between the get() argument and the declared key type <K> of the map.)
他们本可以引入一个额外的方法,比如get_checked(
The arguments relating to the semantics of .equals() are bogus, I think. (Technically they're correct, but I still think they're bogus. No designer in his right mind is ever going to make o1.equals(o2) true if o1 and o2 do not have any common superclass.)
我认为,与.equals()的语义有关的论点是假的。 (从技术上讲,他们是正确的,但我仍然认为他们是假的。如果o1和o2没有任何共同的超类,那么他的正确思想中的设计师永远不会使o1.equals(o2)成立。)
#11
0
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.
在检查构建之后不要忘记删除此接口,因为这不是您在运行时所需的。