决定在java.util.Map<K, V>的接口中没有完全通用的get方法的原因是什么 .
为了澄清这个问题,方法的签名是
V get(Object key)
代替
V get(K key)
我想知道为什么(同样的事情 remove, containsKey, containsValue
) .
决定在java.util.Map<K, V>的接口中没有完全通用的get方法的原因是什么 .
为了澄清这个问题,方法的签名是
V get(Object key)
代替
V get(K key)
我想知道为什么(同样的事情 remove, containsKey, containsValue
) .
11 回答
我在看这个,并想着他们为什么这样做 . 我只是让新的通用接口只接受密钥的正确类型 . 实际的原因是,即使他们引入了泛型,他们也没有创建新的界面 . Map接口与旧的非泛型Map相同,它只用作通用和非泛型版本 . 这样,如果你有一个接受非泛型Map的方法,你可以传递一个
Map<String, Customer>
,它仍然可以工作 . 同时get的 Contract 接受Object,所以新的接口也应该支持这个 Contract .在我看来,他们应该添加一个新的接口,并在现有的集合上实现,但他们决定支持兼容的接口,即使这意味着更糟糕的get方法设计 . 请注意,集合本身将与现有方法兼容,只有接口不兼容 .
我们刚刚进行了大量重构,我们错过了这个强类型的get()来检查我们是否没有错过旧类型的get() .
但是我找到了编译时间检查的解决方法/丑陋技巧:使用强类型get,containsKey,remove ...创建Map接口,并将其放到项目的java.util包中 .
你会得到编译错误只是为了调用get(),...有错误的类型,其他一切似乎都适合编译器(至少在eclipse kepler中) .
在检查构建之后不要忘记删除此接口,因为这不是您在运行时所需的 .
正如其他人所说,
get()
等的原因并不是通用的,因为您要检索的条目的键不必与传入get()
的对象的类型相同;方法的规范只要求它们相等 . 这取决于equals()
方法如何将Object作为参数,而不仅仅是与对象相同的类型 .尽管通常可以确定许多类已定义
equals()
,以使其对象只能等于其自己的类的对象,但Java中有许多地方不是这种情况 . 例如,List.equals()
的规范说如果两个List对象都是Lists并且具有相同的内容,则它们是相等的,即使它们是List
的不同实现 . 所以回到这个问题的例子,根据方法的规范可以有Map<ArrayList, Something>
并且我可以用LinkedList
作为参数调用get()
,它应该检索具有相同内容的列表的键 . 如果get()
是通用的并限制其参数类型,则这是不可能的 .谷歌的一位杰出的Java程序员Kevin Bourrillion在一段时间之前写了一篇关于这个问题的确切信息(不可否认的是
Set
而不是Map
) . 最相关的句子:我认为值得遵循博客文章中的推理 . (提到.NET之后,它在.NET中不是一个问题,那就是在.NET中存在更大的问题,因为方差更有限......)
Contract 表达如下:
(我的重点)
因此,成功的键查找取决于输入键的相等方法的实现 . 这不一定取决于k的类别 .
这是Postel's Law, "be conservative in what you do, be liberal in what you accept from others."的应用
无论何种类型,都可以进行平等检查;
equals
方法在Object
类上定义,并接受任何Object
作为参数 . 因此,关键等价和基于键等价的操作接受任何Object
类型是有意义的 .当映射返回键值时,通过使用type参数,它可以保留尽可能多的类型信息 .
我认为Generics Tutorial的这一部分解释了这种情况(我的重点):
“您需要确保通用API不会过度限制;它必须继续支持API的原始 Contract . 再考虑一下java.util.Collection中的一些示例 . 预通用API如下所示:
一种天真的尝试,它是:
While this is certainly type safe, it doesn’t live up to the API’s original contract. containsAll()方法适用于任何类型的传入集合 . 只有当传入的集合实际上只包含E的实例时,它才会成功,但是:
传入集合的静态类型可能不同,可能是因为调用者不知道传入的集合的精确类型,或者可能是因为它是Collection <S>,其中S是E的子类型 .
使用不同类型的集合调用containsAll()是完全合法的 . 例程应该有效,返回错误 . “
原因是包含由
equals
和hashCode
确定,它们是Object
上的方法,并且都采用Object
参数 . 这是Java 's standard libraries. Coupled with limitations in Java'类型系统中的早期设计缺陷,它强制依赖于equals和hashCode的任何东西都需要Object
.在Java中使用类型安全的哈希表和相等的唯一方法是避免
Object.equals
和Object.hashCode
并使用泛型替代 . Functional Java仅为此目的提供了类型类:Hash<A>和Equal<A> . 提供HashMap<K, V>的包装器,其中包含Hash<K>
和Equal<K>
它的构造函数 . 因此,该类的get
和contains
方法采用类型为K
的泛型参数 .例:
还有一个重要的原因,它不能在技术上完成,因为它破坏了Map .
Java具有多态通用结构,如
<? extends SomeClass>
. 标记为此类引用可以指向使用<AnySubclassOfSomeClass>
签名的类型 . 但是多态通用使得引用 readonly . 编译器允许您仅将泛型类型用作返回类型的方法(如简单的getter),但是使用泛型类型为参数的方法(如普通的setter) . 这意味着如果你编写Map<? extends KeyType, ValueType>
,编译器不允许你调用方法get(<? extends KeyType>)
,并且 Map 将是无用的 . 唯一的解决方案是使此方法不通用:get(Object)
.兼容性 .
在提供泛型之前,只有get(Object o) .
如果他们改变了这个方法来获得(<K> o),它可能会迫使大量代码维护到java用户只是为了再次编译工作代码 .
他们本可以引入一个额外的方法,比如get_checked(<K> o)并弃用旧的get()方法,以便有一个更温和的过渡路径 . 但出于某种原因,这还没有完成 . (我们现在的情况是你需要安装像findBugs这样的工具来检查get()参数和 Map 的声明密钥类型<K>之间的类型兼容性 . )
我认为,与.equals()的语义有关的论点是假的 . (从技术上讲,他们是正确的,但我仍然认为他们是假的 . 如果o1和o2没有任何共同的超类,那么他的正确思想中的设计师永远不会使o1.equals(o2)成立 . )
我认为向后兼容性 .
Map
(或HashMap
)仍然需要支持get(Object)
.