Map.get(Object key) 不(完全)通用的原因是什么
决定不使用完全通用的 get 方法的原因是什么
在 接口中java.util.Map
。
为了澄清问题,该方法的签名是
V get(Object key)
而不是
V get(K key)
我想知道为什么(对于 V get(Object key)
也是如此)代码>删除,包含键,包含值)。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
我认为泛型教程的这一部分解释了这种情况(我的重点):
“您需要确保泛型 API 没有过度限制;它必须
继续支持API原有合约。再考虑一些例子
来自 java.util.Collection。预泛型 API 看起来像:
一个天真的尝试将其泛化为:
虽然这确实是类型安全的,但它不符合 API 的原始约定。
containsAll() 方法适用于任何类型的传入集合。只会
如果传入集合确实只包含 E 的实例,则成功,但是:
收集可能会有所不同,也许
因为调用者不知道
集合的精确类型
传入,或者也许因为它是
集合
,其中 S 是E 的子类型,
调用 containsAll() 是合法的
不同类型的集合。这
例程应该有效,返回 false。”
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:
A naive attempt to generify it is:
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:
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.
legitimate to call containsAll() with
a collection of a different type. The
routine should work, returning false."
兼容性。
在泛型可用之前,只有 get(Object o)。
如果他们将此方法更改为 get( o),则可能会迫使 Java 用户进行大量代码维护,只是为了使工作代码再次编译。
他们可以引入一个附加方法,例如get_checked( o)并弃用旧的get()方法,以便有一个更温和的过渡路径。但由于某种原因,这并没有完成。 (我们现在的情况是,您需要安装像 findBugs 这样的工具来检查 get() 参数和映射的声明键类型 之间的类型兼容性。)
与 .equals 语义相关的参数我认为 () 是假的。 (从技术上讲,它们是正确的,但我仍然认为它们是假的。如果 o1 和 o2 没有任何共同的超类,那么任何头脑正常的设计师都不会让 o1.equals(o2) 为真。)
Compatibility.
Before generics were available, there was just 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.)
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
和hashCode
确定的,它们是Object
上的方法,并且都采用Object
参数。这是 Java 标准库的早期设计缺陷。再加上 Java 类型系统的限制,它强制任何依赖 equals 和 hashCode 的东西都采用Object
。在 Java 中拥有类型安全哈希表和相等性的唯一方法是避开
Object.equals
和Object.hashCode
并使用通用替代品。 Functional Java 附带了用于此目的的类型类:哈希
和
等于
。
HashMap 的包装器
提供了在其中采用Hash
和Equal
构造函数。因此,此类的get
和contains
方法采用K
类型的泛型参数。例子:
The reason is that containment is determined by
equals
andhashCode
which are methods onObject
and both take anObject
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 takeObject
.The only way to have type-safe hash tables and equality in Java is to eschew
Object.equals
andObject.hashCode
and use a generic substitute. Functional Java comes with type classes for just this purpose:Hash<A>
andEqual<A>
. A wrapper forHashMap<K, V>
is provided that takesHash<K>
andEqual<K>
in its constructor. This class'sget
andcontains
methods therefore take a generic argument of typeK
.Example:
还有一个更重要的原因,它在技术上无法完成,因为它破坏了地图。
Java 具有多态泛型结构,如
。标记的此类引用可以指向用
签名的类型。但多态泛型使该引用成为只读。编译器允许您仅使用泛型类型作为方法的返回类型(如简单的 getter),但会阻止使用泛型类型为参数的方法(如普通的 setter)。这意味着如果你写
Map
,编译器不允许你调用方法get()
,这样映射就没有用了。唯一的解决方案是使该方法不再通用:get(Object)
。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 methodget(<? extends KeyType>)
, and the map will be useless. The only solution is to make this method not generic:get(Object)
.我猜是向后兼容。
Map
(或HashMap
)仍然需要支持get(Object)
。Backwards compatibility, I guess.
Map
(orHashMap
) still needs to supportget(Object)
.我看着这个并思考他们为什么这样做。我认为现有的任何答案都不能解释为什么他们不能让新的通用接口仅接受正确的密钥类型。真正的原因是,即使他们引入了泛型,他们也没有创建新的接口。 Map 接口与旧的非通用 Map 相同,它只是充当通用和非通用版本。这样,如果您有一个接受非通用 Map 的方法,您可以向它传递一个
Map
并且它仍然可以工作。同时 get 的契约接受 Object,因此新接口也应该支持这个契约。在我看来,他们应该添加一个新接口并在现有集合上实现这两个接口,但他们决定支持兼容接口,即使这意味着 get 方法的设计更糟糕。请注意,集合本身与现有方法兼容,只有接口不兼容。
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.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() 来检查我们是否错过了一些旧类型的 get() 。
但我发现了编译时间检查的解决方法/丑陋技巧:使用强类型 get、containsKey、remove... 创建 Map 接口,并将其放入项目的 java.util 包中。
你会得到编译错误只是为了调用 get(),...使用错误的类型,其他一切对于编译器来说似乎都可以(至少在 eclipse kepler 内)。
检查构建后不要忘记删除此接口,因为这不是您在运行时想要的。
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.
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.
You will get compilation errors just for calling get(), ... with wrong types, everything others seems ok for compiler (at least inside eclipse kepler).
Do not forget to delete this interface after check of your build as this is not what you want in runtime.
正如其他人所提到的,
get()
等之所以不通用,是因为您要检索的条目的键不必与您传递给 < 的对象具有相同的类型。代码>get();该方法的规范仅要求它们相等。这是从equals()
方法如何接受一个对象作为参数得出的,而不仅仅是与对象相同的类型。虽然许多类都定义了 equals() ,因此它的对象只能等于它自己类的对象,但在 Java 中很多地方情况并非如此。例如,
List.equals()
的规范规定,如果两个 List 对象都是列表并且具有相同的内容,则它们相等,即使它们是List
的不同实现>。因此,回到这个问题中的示例,根据方法的规范,可以有一个Map
并让我调用get()
以 LinkedList 作为参数,它应该检索具有相同内容的列表的键。如果get()
是通用的并且限制其参数类型,则这是不可能的。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 toget()
; the specification of the method only requires that they be equal. This follows from how theequals()
method takes in an Object as parameter, not just the same type as the 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 forList.equals()
says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations ofList
. So coming back to the example in this question, according to the specification of the method is possible to have aMap<ArrayList, Something>
and for me to callget()
with aLinkedList
as argument, and it should retrieve the key which is a list with the same contents. This would not be possible ifget()
were generic and restricted its argument type.Google 的一位出色的 Java 程序员 Kevin Bourrillion 在 博客文章 不久前(诚然,在
Set
而不是Map
的上下文中)。最相关的一句话:我不完全确定我是否同意这一原则 - 例如,.NET 似乎可以要求正确的密钥类型 - 但值得遵循博客文章中的推理。 (提到 .NET,值得解释的是,它在 .NET 中不是问题的部分原因是 .NET 中存在更大的问题,且方差更有限......)
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 ofMap
). The most relevant sentence: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...)
合同表述如下:
(我的重点)
因此,成功的键查找取决于输入键对相等方法的实现。这不一定取决于 k 的类别。
The contract is expressed thus:
(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.
这是Postel定律的应用,“在你所做的事情上要保守” ,对你从别人那里接受的东西要宽容。”
无论类型如何,都可以执行相等性检查;
equals
方法在Object
类上定义,并接受任何Object
作为参数。因此,键等价以及基于键等价的操作接受任何Object
类型都是有意义的。当映射返回键值时,它通过使用类型参数来保留尽可能多的类型信息。
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 theObject
class and accepts anyObject
as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept anyObject
type.When a map returns key values, it conserves as much type information as it can, by using the type parameter.