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接口中没有完全通用的get方法的原因是什么?< K、V >地图。
To clarify the question, the signature of the method is
为了澄清问题,方法的签名是
V get(Object key)
V获得(对象键)
instead of
而不是
V get(K key)
V得到(K键)
and I'm wondering why (same thing for remove, containsKey, containsValue
).
我想知道为什么(移除、containsKey和containsValue也是如此)。
11 个解决方案
#1
230
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 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()的规范说,如果两个列表对象都是列表,并且具有相同的内容,即使它们是列表的不同实现,那么它们是相等的。回到这个问题的例子,根据这个方法的规范,可以有一个Map
#2
94
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集合框架(和谷歌集合库)的方法从不限制其参数的类型,除非有必要防止集合被破坏。
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
25
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."
这是波斯特尔定律的一个应用,“在你做的事情上要保守,在你接受别人的事情上要*。”
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方法在对象类上定义,并接受任何对象作为参数。因此,对于键等价和基于键等价的操作来说,接受任何对象类型都是有意义的。
When a map returns key values, it conserves as much type information as it can, by using the type parameter.
当一个映射返回键值时,它通过使用type参数来保存尽可能多的类型信息。
#5
10
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.
-
传入集合的静态类型可能有所不同,这可能是因为调用者不知道要传入的集合的确切类型,也可能是因为它是一个集合
,其中S是E的子类型。 - It’s perfectly legitimate to call containsAll() with a collection of a different type. The routine should work, returning false."
- 使用不同类型的集合调用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确定的,它们都是对象上的方法,并且都接受一个对象参数。这是Java标准库的早期设计缺陷。加上Java类型系统的限制,它强制任何依赖于equals和hashCode的对象。
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中拥有类型安全哈希表和等式的唯一方法是避免对象。=和对象。hashCode并使用泛型替代。函数式Java具有这样的类型类:Hash,等于。HashMap
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
#7
4
There is one more weighty reason, it can not be done technically, because it brokes 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
2
Compatibility.
兼容性。
Before generics were available, there was just get(Object o).
在泛型可用之前,只有get(对象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_check (
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)成为现实。
#9
1
Backwards compatibility, I guess. Map
(or HashMap
) still needs to support get(Object)
.
我想向后兼容性。Map(或HashMap)仍然需要支持get(Object)。
#10
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接口是相同的旧非泛型映射,它只是作为泛型和非泛型的版本。这样,如果您有一个接受非泛型映射的方法,您可以将映射
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方法的设计更糟糕。注意,集合本身将与现有方法兼容,只有接口不能兼容。
#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…然后放到java中。你项目的包装。
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
230
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 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()的规范说,如果两个列表对象都是列表,并且具有相同的内容,即使它们是列表的不同实现,那么它们是相等的。回到这个问题的例子,根据这个方法的规范,可以有一个Map
#2
94
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集合框架(和谷歌集合库)的方法从不限制其参数的类型,除非有必要防止集合被破坏。
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
25
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."
这是波斯特尔定律的一个应用,“在你做的事情上要保守,在你接受别人的事情上要*。”
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方法在对象类上定义,并接受任何对象作为参数。因此,对于键等价和基于键等价的操作来说,接受任何对象类型都是有意义的。
When a map returns key values, it conserves as much type information as it can, by using the type parameter.
当一个映射返回键值时,它通过使用type参数来保存尽可能多的类型信息。
#5
10
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.
-
传入集合的静态类型可能有所不同,这可能是因为调用者不知道要传入的集合的确切类型,也可能是因为它是一个集合
,其中S是E的子类型。 - It’s perfectly legitimate to call containsAll() with a collection of a different type. The routine should work, returning false."
- 使用不同类型的集合调用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确定的,它们都是对象上的方法,并且都接受一个对象参数。这是Java标准库的早期设计缺陷。加上Java类型系统的限制,它强制任何依赖于equals和hashCode的对象。
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中拥有类型安全哈希表和等式的唯一方法是避免对象。=和对象。hashCode并使用泛型替代。函数式Java具有这样的类型类:Hash,等于。HashMap
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
#7
4
There is one more weighty reason, it can not be done technically, because it brokes 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
2
Compatibility.
兼容性。
Before generics were available, there was just get(Object o).
在泛型可用之前,只有get(对象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_check (
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)成为现实。
#9
1
Backwards compatibility, I guess. Map
(or HashMap
) still needs to support get(Object)
.
我想向后兼容性。Map(或HashMap)仍然需要支持get(Object)。
#10
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接口是相同的旧非泛型映射,它只是作为泛型和非泛型的版本。这样,如果您有一个接受非泛型映射的方法,您可以将映射
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方法的设计更糟糕。注意,集合本身将与现有方法兼容,只有接口不能兼容。
#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…然后放到java中。你项目的包装。
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.
在检查构建之后不要忘记删除这个接口,因为这不是您在运行时想要的。