为什么Java集合删除方法通用?
为什么不是Collection.remove(Object o)generics?
看起来像Collection<E>
可以有boolean remove(E o);
然后,当您不小心尝试从Collection<String>
删除(例如) Set<String>
而不是每个单独的String时,以后将是编译时错误而不是debugging问题。
Josh Bloch和Bill Pugh在“ Java Puzzlers IV:Phantom Reference Menace”,“克隆人的攻击”和“轮class的复仇”中提到了这个问题。
Josh Bloch说(6:41)他们试图生成Map的get方法,删除方法和其他一些方法,但是“它根本行不通”。
如果只允许集合的genericstypes作为参数types,则有太多合理的程序不能被聚合。 他给出的例子是Number
List
和Long
List
的交集。
remove()
(在Map
以及Collection
)不是generics的,因为你应该能够传入任何types的对象来remove()
。 删除的对象不必与您传递给remove()
的对象的types相同。 它只要求它们是平等的。 从remove()
的规范中, remove(o)
移除对象e
,使得(o==null ? e==null : o.equals(e))
为真。 请注意,没有要求o和e是相同的types。 这是由于equals()
方法接受一个Object作为参数,而不是与该对象相同的types。
虽然通常情况下许多类都定义了equals()
所以它的对象只能等于它自己类的对象,但事实并非总是如此。 例如,List.equals()的规范说,如果两个List对象都是List并且具有相同的内容,即使它们是List的不同实现,也是相等的。 所以回到这个问题的例子中,有可能有一个Map<ArrayList, Something>
,我可以用LinkedList
作为参数调用remove()
,它应该删除一个具有相同内容的列表。 如果remove()
是generics的并且限制了它的参数types,那么这是不可能的。
因为如果你的types参数是通配符,你不能使用通用的remove方法。
我似乎回想起用Map的get(Object)方法来解决这个问题。 在这种情况下,get方法不是通用的,尽pipe它应该合理地期望传递与第一个types参数相同types的对象。 我意识到,如果您将通配符作为第一个typesparameter passing给Maps,那么如果该参数是generics的,则无法使用该方法从Map获取元素。 通配符参数不能真正满足,因为编译器不能保证types是正确的。 我推测,添加的原因是通用的,你需要保证types是正确的,然后将其添加到集合。 但是,当删除一个对象,如果types不正确,那么它不会匹配任何东西。 如果参数是通配符,那么即使您可能拥有一个可保证属于该集合的对象,因为您刚刚在上一行中获得了对该对象的引用,该方法仍然无法使用。
我可能没有解释得很好,但对我来说似乎是合乎逻辑的。
除了其他答案之外,还有另一个原因为什么方法应该接受一个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<Employee>(); // ... // 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
方法。 构build谓词变得非常简单。
假设有一个Cat
的集合,以及Animal
, Cat
, SiameseCat
和Dog
types的一些对象引用。 询问集合是否包含由Cat
或SiameseCat
引用引用的对象似乎是合理的。 询问它是否包含Animal
参考所提及的对象可能看起来不妥,但仍然是完全合理的。 所讨论的对象毕竟可能是一只Cat
,并可能出现在集合中。
此外,即使物体恰好是Cat
以外的物体,也不会在收集物中出现问题,只需回答“不,不”。 某种types的“lookup-style”集合应该能够有意义地接受任何超types的引用,并确定该对象是否存在于集合中。 如果传入的对象引用是不相关的types,那么集合就不可能包含它,所以查询在某种意义上是没有意义的(它总是回答“否”)。 尽pipe如此,由于没有办法将参数限制为子types或超types,所以最简单地接受任何types并且对于与集合types无关的任何对象回答“否”。
我总是认为这是因为remove()没有理由关心你给它什么types的对象。 无论如何,检查该对象是否为Collection所包含的对象之一是很容易的,因为它可以在任何情况下调用equals()。 有必要在add()上检查types,以确保它只包含该types的对象。
删除不是一个通用的方法,以便使用非generics集合的现有代码将仍然编译,仍然具有相同的行为。
有关详细信息,请参阅http://www.ibm.com/developerworks/java/library/j-jtp01255.html 。
编辑:评论者问为什么添加方法是通用的。 […删除了我的解释…]第二位评论者比我更好地回答了firebird84的问题。
另一个原因是因为接口。 这里是一个例子来显示它:
public interface A {} public interface B {} public class MyClass implements A, B {} public static void main(String[] args) { Collection<A> collection = new ArrayList<>(); MyClass item = new MyClass(); collection.add(item); // works fine B b = item; // valid collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */ }
因为它会破坏现有的(Java5之前的)代码。 例如,
Set stringSet = new HashSet(); // do some stuff... Object o = "foobar"; stringSet.remove(o);
现在你可能会说上面的代码是错误的,但是假设o来自一个异构的对象集合(即它包含string,数字,对象等)。 你想删除所有的匹配,这是合法的,因为删除会忽略非string,因为它们是不相等的。 但是,如果你把它删除(stringo),不再有效。