为什么Collection.remove(Object o)不通用?
似乎Collection<E>
可能有boolean remove(E o);
然后,当您意外尝试删除(例如)Set<String>
而不是Collection<String>
中的每个字符串时,这将是编译时错误,而不是以后的调试问题。
答案 0 :(得分:72)
remove()
(在Map
以及Collection
中)不是通用的,因为您应该能够将任何类型的对象传递给remove()
。删除的对象不必与传递给remove()
的对象的类型相同;它只要求它们是平等的。根据{{1}}的规范,remove()
删除对象remove(o)
,使e
为(o==null ? e==null : o.equals(e))
。请注意,没有任何内容要求true
和o
属于同一类型。这是因为e
方法接受equals()
作为参数,而不仅仅是与对象相同的类型。
虽然,很多类都定义Object
以使其对象只能等于自己类的对象,但通常情况并非总是如此。例如,equals()
的规范表明,如果两个List对象既是列表又具有相同的内容,则它们是相等的,即使它们是List.equals()
的不同实现。回到这个问题中的示例,可以有一个List
并且我可以使用Map<ArrayList, Something>
作为参数调用remove()
,它应该删除密钥具有相同内容的列表。如果LinkedList
是通用的并限制其参数类型,则无法实现这一点。
答案 1 :(得分:68)
Josh Bloch和Bill Pugh在Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone, and Revenge of The Shift中提到了这个问题。
Josh Bloch说(6:41)他们试图使get方法变得一致 地图,删除方法和其他一些,但“它根本没有用”。
如果有太多合理的程序无法通知
您只允许集合的泛型类型作为参数类型。
他给出的例子是List
Number
和List
的交集
Long
的{{1}}。
答案 2 :(得分:11)
因为如果您的类型参数是通配符,则不能使用通用的删除方法。
我似乎回想起使用Map的get(Object)方法遇到这个问题。在这种情况下,get方法不是通用的,尽管它应该合理地期望传递与第一个类型参数相同类型的对象。我意识到,如果您使用通配符作为第一个类型参数传递Maps,那么如果该参数是通用的,则无法使用该方法从Map中获取元素。通配符参数不能真正满足,因为编译器无法保证类型是正确的。我推测add的一般原因是你需要在将它添加到集合之前保证类型是正确的。但是,在删除对象时,如果类型不正确,则无论如何都不会匹配任何内容。如果参数是通配符,那么该方法将无法使用,即使您可能有一个GUARANTEE属于该集合的对象,因为您只是在上一行中有对它的引用....
我可能没有很好地解释它,但对我来说似乎合乎逻辑。
答案 3 :(得分:6)
除了其他答案之外,还有另一个原因,即该方法应该接受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
方法。通过这种方式构建谓词变得非常简单。
答案 4 :(得分:5)
假设有一个Cat
集合,以及类型Animal
,Cat
,SiameseCat
和Dog
的一些对象引用。询问集合是否包含Cat
或SiameseCat
引用所引用的对象似乎是合理的。询问它是否包含Animal
引用引用的对象可能看起来很狡猾,但它仍然是完全合理的。毕竟,有问题的对象可能是Cat
,可能会出现在集合中。
此外,即使对象恰好是Cat
以外的其他对象,也没有问题说它是否出现在集合中 - 只需回答“不,它没有”。某种类型的“查找样式”集合应该能够有意义地接受任何超类型的引用并确定该对象是否存在于集合中。如果传入的对象引用是不相关的类型,则集合无法包含它,因此查询在某种意义上没有意义(它总是回答“否”)。尽管如此,由于没有任何方法可以将参数限制为子类型或超类型,因此最简单地接受任何类型并对类型与集合类型无关的任何对象回答“否”是最实际的。
答案 5 :(得分:3)
我总是认为这是因为remove()没有理由关心你给它的对象类型。无论如何,检查该对象是否是Collection包含的对象之一是很容易的,因为它可以对任何东西调用equals()。有必要检查add()上的类型以确保它只包含该类型的对象。
答案 6 :(得分:0)
这是一个妥协。两种方法都有其优势:
remove(Object o)
remove(E e)
通过在编译时检测细微的错误(例如错误地尝试从短裤列表中删除整数),为大多数程序想要做的事情带来了更多的类型安全性。向后兼容性一直是发展Java API的主要目标,因此选择remove(Object o)是因为它使生成现有代码更加容易。如果向后兼容性不是问题,那么我猜设计师会选择remove(E e)。
答案 7 :(得分:-1)
Remove不是通用方法,因此使用非泛型集合的现有代码仍然可以编译并且仍然具有相同的行为。
有关详细信息,请参阅http://www.ibm.com/developerworks/java/library/j-jtp01255.html。
编辑:评论者询问为什么add方法是通用的。 [...删除了我的解释......]第二位评论者回答了firebird84的问题,比我好得多。
答案 8 :(得分:-2)
另一个原因是因为接口。这是一个展示它的例子:
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 */
}
答案 9 :(得分:-3)
因为它会破坏现有的(Java5之前的)代码。如,
Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);
现在你可能会说上面的代码是错误的,但是假设o来自异构的一组对象(即它包含字符串,数字,对象等)。你想要删除所有匹配,这是合法的,因为删除只会忽略非字符串,因为它们是不相等的。但如果你删除它(String o),那就不再有用了。