为什么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 ListLong 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的集合,以及AnimalCatSiameseCatDogtypes的一些对象引用。 询问集合是否包含由CatSiameseCat引用引用的对象似乎是合理的。 询问它是否包含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),不再有效。