[英]Why aren't Java Collections remove methods generic?
为什么Collection.remove(Object o)不是通用的?
似乎Collection<E>
可以有boolean remove(E o);
然后,当您不小心尝试从Collection<String>
删除(例如) Set<String>
而不是每个单独的 String 时,这将是一个编译时错误,而不是以后的调试问题。
remove()
(在Map
和Collection
)不是通用的,因为您应该能够将任何类型的对象传递给remove()
。 删除的对象不必与传入remove()
的对象类型相同; 它只要求它们相等。 根据remove()
的规范, remove(o)
删除对象e
使得(o==null ? e==null : o.equals(e))
为true
。 请注意,没有什么要求o
和e
是相同的类型。 这是因为equals()
方法接受一个Object
作为参数,而不仅仅是与对象的类型相同。
虽然,许多类都定义了equals()
以便它的对象只能等于它自己类的对象,这可能是普遍的事实,但情况并非总是如此。 例如, List.equals()
的规范规定,如果两个 List 对象都是 List 并且具有相同的内容,则它们是相等的,即使它们是List
不同实现。 因此,回到这个问题中的示例,可以有一个Map<ArrayList, Something>
并且我可以使用LinkedList
作为参数调用remove()
,它应该删除具有相同内容的列表的键. 如果remove()
是通用的并且限制了它的参数类型,这将是不可能的。
Josh Bloch 和 Bill Pugh 在Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone, and Revenge of The Shift 中提到了这个问题。
Josh Bloch 说 (6:41) 他们试图对 Map 的 get 方法、remove 方法和其他一些方法进行泛化,但“它根本不起作用”。
如果只允许集合的泛型类型作为参数类型,那么有太多合理的程序无法泛型。 他举的例子是一个List
of Number
s和一个List
of Long
s的交集。
因为如果您的类型参数是通配符,则不能使用通用的 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<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
方法。 通过这种方式构建谓词变得非常简单。
假设有一个Cat
集合,以及一些类型为Animal
、 Cat
、 SiameseCat
和Dog
对象引用。 询问集合是否包含Cat
或SiameseCat
引用所引用的对象似乎是合理的。 询问它是否包含Animal
引用所引用的对象可能看起来很狡猾,但它仍然是完全合理的。 毕竟,有问题的对象可能是Cat
,并且可能出现在集合中。
此外,即使对象碰巧不是Cat
,也可以说它是否出现在集合中 - 只需回答“不,它没有”。 某种类型的“查找样式”集合应该能够有意义地接受任何超类型的引用并确定该对象是否存在于集合中。 如果传入的对象引用是不相关的类型,则集合不可能包含它,因此查询在某种意义上没有意义(它总是回答“否”)。 尽管如此,由于没有任何方法可以将参数限制为子类型或超类型,因此最实用的做法是简单地接受任何类型并为类型与集合无关的任何对象回答“否”。
我一直认为这是因为 remove() 没有理由关心你给它什么类型的对象。 无论如何,检查该对象是否是 Collection 包含的对象之一很容易,因为它可以对任何对象调用 equals()。 有必要在 add() 上检查类型以确保它只包含该类型的对象。
这是一种妥协。 这两种方法各有优势:
remove(Object o)
remove(E e)
通过在编译时检测细微的错误,为大多数程序想要做的事情带来更多的类型安全性,例如错误地尝试从 short 列表中删除一个整数。在开发 Java API 时,向后兼容性始终是一个主要目标,因此选择 remove(Object o) 是因为它使现有代码的泛化变得更加容易。 如果向后兼容性不是问题,我猜设计者会选择 remove(E e)。
Remove 不是通用方法,因此使用非通用集合的现有代码仍然可以编译并仍然具有相同的行为。
有关详细信息,请参阅http://www.ibm.com/developerworks/java/library/j-jtp01255.html 。
编辑:评论者询问为什么 add 方法是通用的。 [...删除了我的解释...] 第二个评论者比我更好地回答了 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 来自一组异构的对象(即,它包含字符串、数字、对象等)。 您想删除所有匹配项,这是合法的,因为 remove 只会忽略非字符串,因为它们不相等。 但是如果你让它 remove(String o),那将不再有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.