什么是决定不具有的接口完全通用的get方法背后的原因java.util.Map<K, V>
。
为了澄清问题,该方法的签名是
V get(Object key)
代替
V get(K key)
我想知道为什么(与相同remove, containsKey, containsValue
)。
什么是决定不具有的接口完全通用的get方法背后的原因java.util.Map<K, V>
。
为了澄清问题,该方法的签名是
V get(Object key)
代替
V get(K key)
我想知道为什么(与相同remove, containsKey, containsValue
)。
Answers:
正如其他人所提到的,之类的原因get()
不是通用的,因为您要检索的条目的键不必与您传递给的对象的类型相同get()
;方法的规范仅要求它们相等。这从equals()
方法如何将对象作为参数(而不仅仅是与对象相同的类型)中得出。
尽管通常已经equals()
定义了许多类,以便其对象只能等于其自己的类的对象,但是在Java中有很多地方并非如此。例如,规范List.equals()
指出两个List对象都是相同的,并且都是相同的内容,即使它们都是的不同实现,也是如此List
。因此,回到这个问题的示例中,根据方法的规范,可以使用a 作为参数,并且Map<ArrayList, Something>
我可以get()
使用LinkedList
as参数调用它,并且它应该检索具有相同内容的列表的键。如果get()
是通用的并且限制了它的参数类型,那将是不可能的。
m.get(linkedList)
,为什么不将m
类型定义为Map<List,Something>
?我想不出一个用例,在m.get(HappensToBeEqual)
不更改Map
类型以获取接口的情况下进行调用是有意义的。
TreeMap
当您将错误类型的对象传递给get
方法时,可能会失败,但有时会失败,例如,当地图碰巧为空时。甚至更糟的是,如果提供Comparator
了该compare
方法(具有通用签名!),则可能会使用错误类型的参数来调用该方法,而不会发出任何未经检查的警告。这是坏行为。
Google的一位出色的Java程序员Kevin Bourrillion 在不久前的一篇博客文章中(关于Set
而不是Map
)就此问题进行了准确的撰写。最相关的句子:
一致地,除非有必要防止集合损坏,否则Java Collections Framework(以及Google Collections Library)的方法从不限制其参数类型。
我不能完全确定我是否同意它是一个原则-例如,.NET似乎要求正确的密钥类型就可以了-但值得遵循博客文章中的推理。(提到.NET时,值得解释的是,.NET中不存在问题的部分原因是,.NET中存在更大的问题,而方差有限。)
Integer
和a Double
永远不能彼此相等,但询问a是否Set<? extends Number>
包含值仍然是一个公平的问题new Integer(5)
。
Set<? extends Foo>
。我经常更改地图的键类型,然后感到沮丧的是,编译器无法找到需要更新代码的所有位置。我真的不相信这是正确的权衡。
合同表示如下:
更正式地讲,如果此映射包含从键k到值v的映射,使得(key == null?k == null: key.equals(k)),则此方法返回v;否则,返回v。否则返回null。(最多可以有一个这样的映射。)
(我的重点)
因此,成功的键查找取决于输入键对等式方法的实现。那不一定取决于k的类别。
hashCode()
。如果没有正确实现hashCode(),那么equals()
在这种情况下,很好的实现就毫无用处。
get()
不需要采用类型的参数Object
即可满足联系。假设get方法被限制为键类型K
-合同仍然有效。当然,编译时间类型不是其子类的使用K
现在将无法编译,但这不会使合同无效,因为合同隐式地讨论了代码编译时会发生什么。
这是Postel法则的应用, “对您的工作要保守,对别人接受的东西要开放。”
不论类型如何,都可以执行相等性检查。该equals
方法在Object
类上定义,并且可以接受任何Object
参数。因此,对于密钥等效性以及基于密钥等效性的操作来说,接受任何Object
类型。
映射返回键值时,通过使用type参数,它可以保存尽可能多的类型信息。
V Get(K k)
在C#中,因为它也很有意义。Java和.NET方法之间的区别实际上仅是谁阻止了不匹配的东西。在C#中是编译器,在Java中是集合。大约在同时,.NET的不一致的集合类,一旦我的愤怒,但Get()
并Remove()
只接受一个匹配的类型肯定可以防止您在无意中传递一个错误的值。
contains : K -> boolean
。
我认为“泛型教程”的这一部分解释了这种情况(我的重点是):
“您需要确保通用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实例时,它才会成功,但是:
containsAll( Collection< ? extends E > c )
呢?
containsAll
与Collection<S>
其中S
是一个超型的E
。如果允许的话,这是不允许的containsAll( Collection< ? extends E > c )
。此外,作为是在本例中明确指出,这是合法的传递不同类型的集合(有则返回值是false
)。
原因是遏制是由方法决定的equals
,hashCode
哪种方法Object
都采用Object
参数。这是Java标准库中的早期设计缺陷。再加上Java的类型系统中的限制,它迫使任何依赖于equals和hashCode的值接受Object
。
在Java中具有类型安全的哈希表和相等性的唯一方法是避免 Object.equals
与Object.hashCode
和使用一个通用的替代品。为此,功能Java随附类型类:Hash<A>
和Equal<A>
。HashMap<K, V>
提供了for的包装,该包装采用Hash<K>
和Equal<K>
的构造函数。这个类是get
和contains
因此采取的方法类型的通用说法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
兼容性。
在泛型可用之前,只有get(Object o)。
如果他们将这种方法更改为get(<K> o),可能会迫使对Java用户的大规模代码维护只是为了使工作代码再次编译。
他们本可以引入另一种方法,例如说get_checked(<K> o)并弃用旧的get()方法,以便有一条更平缓的过渡路径。但是由于某种原因,这没有完成。(现在的情况是,您需要安装诸如findBugs之类的工具,以检查get()参数与地图的声明键类型<K>之间的类型兼容性。)
我认为与.equals()的语义有关的参数是虚假的。(从技术上讲,它们是正确的,但我仍然认为它们是虚假的。如果o1和o2没有任何共同的超类,那么在他的脑海中就不会有任何设计师可以使o1.equals(o2)为真。)
还有一个重要的原因,因为它破坏了Map,因此在技术上无法完成。
Java具有类似的多态通用构造<? extends SomeClass>
。标记为此类引用可以指向使用签名的类型<AnySubclassOfSomeClass>
。但是多态泛型使该引用变为只读。编译器仅允许您将泛型类型用作方法的返回类型(例如简单的getter),但会阻止使用泛型为参数的方法(例如普通的setter)。这意味着如果您编写Map<? extends KeyType, ValueType>
,编译器将不允许您调用method get(<? extends KeyType>)
,并且映射将无用。唯一的解决方案是使此方法不通用:get(Object)
。
我想是向后兼容。Map
(或HashMap
)仍然需要支持get(Object)
。
put
(这确实限制了泛型类型)。通过使用原始类型,您可以获得向后兼容性。泛型是“选择加入”。
我看着这个,想着为什么他们要这样做。我不认为任何现有答案都可以解释为什么它们不能仅使新的通用接口仅接受密钥的正确类型。实际原因是,即使他们引入了泛型,他们也没有创建新接口。Map接口与旧的非通用Map相同,仅用作通用和非通用版本。这样,如果您有一个接受非泛型Map的方法,则可以将其传递给a Map<String, Customer>
,它仍然可以工作。同时,获取合同接受对象,因此新接口也应支持该合同。
在我看来,他们应该添加一个新接口并在现有集合上都实现,但是他们决定支持兼容接口,即使这意味着对get方法进行更糟糕的设计。请注意,集合本身将与现有方法兼容,仅接口不兼容。