为什么Collection.remove(Object o)不是通用的?
似乎Collection
可能有boolean remove(E o);
然后,当您意外地尝试Set
从a中删除(例如)而不是每个单独的String时Collection
,这将是编译时错误,而不是稍后的调试问题.
remove()
(in in Map
以及in Collection
)不是通用的,因为你应该能够传入任何类型的对象remove()
.删除的对象不必与传入的对象的类型相同remove()
; 它只要求它们是平等的.从本说明书中remove()
,remove(o)
将删除对象e
,使得(o==null ? e==null : o.equals(e))
是true
.请注意,没有任何要求o
和e
相同的类型.这是因为该equals()
方法接受Object
as参数,而不仅仅是与对象相同的类型.
虽然,通常equals()
可以确定许多类已经定义,以便其对象只能等于其自己的类的对象,但情况肯定并非总是如此.例如,List.equals()
如果两个List对象都是Lists并且具有相同的内容,则表示两个List对象相等的规范,即使它们是不同的实现List
.所以回来的例子在这个问题上,可以有一个Map
和我打电话给remove()
一个LinkedList
作为参数,并应删除这与相同内容的列表的关键.如果remove()
是通用的并且限制其参数类型,则这是不可能的.
Josh Bloch和Bill Pugh在Java Puzzlers IV中提到了这个问题:幻影参考威胁,克隆攻击和转变复仇.
Josh Bloch说(6:41)他们试图通过格式化get方法,删除方法和其他方法,但"它根本不起作用".
如果只允许集合的泛型类型作为参数类型,则有太多合理的程序无法生成.由他给出的例子是一个交叉点List
的Number
S和
List
的Long
第
因为如果您的类型参数是通配符,则不能使用通用的remove方法.
我似乎回想起使用Map的get(Object)方法遇到这个问题.在这种情况下,get方法不是通用的,尽管它应该合理地期望传递与第一个类型参数相同类型的对象.我意识到,如果您使用通配符作为第一个类型参数传递Maps,那么如果该参数是通用的,则无法使用该方法从Map中获取元素.通配符参数不能真正满足,因为编译器无法保证类型是正确的.我推测add的一般原因是你需要在将它添加到集合之前保证类型是正确的.但是,在删除对象时,如果类型不正确,则无论如何都不会匹配任何内容.
我可能没有很好地解释它,但它对我来说似乎合乎逻辑.
除了其他答案之外,还有另一个原因Object
,即该方法应该接受一个谓词.请考虑以下示例:
class Person { public String name; // override equals() } class Employee extends Person { public String company; // override equals() } class Developer extends Employee { public int yearsOfExperience; // override equals() } class Test { public static void main(String[] args) { Collection extends Person> people = new ArrayList(); // ... // to remove the first employee with a specific name: people.remove(new Person(someName1)); // to remove the first developer that matches some criteria: people.remove(new Developer(someName2, someCompany, 10)); // to remove the first employee who is either // a developer or an employee of someCompany: people.remove(new Object() { public boolean equals(Object employee) { return employee instanceof Developer || ((Employee) employee).company.equals(someCompany); }}); } }
关键是传递给remove
方法的对象负责定义equals
方法.通过这种方式构建谓词变得非常简单.
假设一个人的集合Cat
,和一些类型的对象引用Animal
,Cat
,SiameseCat
,和Dog
.询问集合是否包含由引用Cat
或SiameseCat
引用引用的对象似乎是合理的.询问它是否包含Animal
引用引用的对象可能看起来很狡猾,但它仍然是完全合理的.毕竟,有问题的对象可能是a Cat
,并且可能出现在集合中.
此外,即使对象恰好是a以外的东西Cat
,也没有问题说它是否出现在集合中 - 简单地回答"不,它没有".某种类型的"查找样式"集合应该能够有意义地接受任何超类型的引用,并确定该对象是否存在于集合中.如果传入的对象引用是不相关的类型,则集合无法包含它,因此查询在某种意义上没有意义(它总是回答"否").尽管如此,由于没有任何方法可以将参数限制为子类型或超类型,因此最简单地接受任何类型并对类型与集合类型无关的任何对象回答"否"是最实际的.