为什么Java Collections没有删除通用的方法?
-
01-07-2019 - |
题
为什么不是 Collection.remove(Object o)泛型?
似乎Collection<E>
可以有boolean remove(E o);
然后,当您不小心尝试删除(例如)Set<String>
而不是Collection<String>
中的每个字符串时,这将是编译时错误,而不是以后的调试问题。
解决方案
Josh Bloch和Bill Pugh在 Java Puzzlers IV:The 幻影参考威胁,克隆人的攻击和复仇 移 。
Josh Bloch说(6:41)他们试图使get方法变得一致 Map,remove方法和其他一些方法,但是<!>“它根本不起作用<!>”;
如果有太多合理的程序无法通知
您只允许集合的泛型类型作为参数类型。
他给出的例子是List
的Number
s和a的交集
Long
of <=> s。
其他提示
remove()
(在Map
以及Collection
中)不是通用的,因为您应该能够将任何类型的对象传递给remove(o)
。删除的对象不必与传入e
的对象的类型相同;它只要求它们是平等的。根据(o==null ? e==null : o.equals(e))
的规范,true
删除对象o
,使equals()
Object
。请注意,没有什么要求List.equals()
和List
是相同的类型。这是因为Map<ArrayList, Something>
方法接受LinkedList
作为参数,而不仅仅是与对象相同的类型。
虽然,通常可以确定许多类已经定义了<=>,以便它的对象只能等于它自己的类的对象,但情况并非总是如此。例如,<=>的规范表明如果两个List对象都是Lists并具有相同的内容,则它们是相等的,即使它们是<=>的不同实现。所以回到这个问题中的例子,可以有一个<=>并且我可以用<=>作为参数调用<=>,它应该删除具有相同内容的列表的键。如果<=>是通用的并且限制了它的参数类型,那么这是不可能的。
因为如果您的类型参数是通配符,则不能使用通用的删除方法。
我似乎回想起使用Map的get(Object)方法遇到这个问题。在这种情况下,get方法不是通用的,尽管它应该合理地期望传递与第一个类型参数相同类型的对象。我意识到,如果您使用通配符作为第一个类型参数传递Maps,那么如果该参数是通用的,则无法使用该方法从Map中获取元素。通配符参数不能真正满足,因为编译器无法保证类型是正确的。我推测add的一般原因是你需要在将它添加到集合之前保证类型是正确的。但是,在删除对象时,如果类型不正确,则无论如何都不会匹配任何内容。如果参数是通配符,那么该方法将无法使用,即使您可能有一个GUARANTEE属于该集合的对象,因为您只是在上一行中有对它的引用....
我可能没有很好地解释它,但对我来说似乎合乎逻辑。
除了其他答案之外,还有另一个原因,即该方法应该接受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
,SiameseCat
,Dog
和<=>的一些对象引用。询问集合是否包含<=>或<=>引用引用的对象似乎是合理的。询问它是否包含<=>引用所引用的对象可能看起来很狡猾,但它仍然是完全合理的。毕竟,有问题的对象可能是<=>,并且可能会出现在集合中。
此外,即使对象恰好是<=>以外的其他对象,也没有问题说它是否出现在集合中 - 只需回答<!>“否,它不会<!>”; 。一个<!> quot; lookup-style <!> quot;某种类型的集合应该能够有意义地接受任何超类型的引用并确定该对象是否存在于集合中。如果传入的对象引用是不相关的类型,则集合无法包含它,因此查询在某种意义上没有意义(它总是回答<!> quot; no <!> quot;)。尽管如此,由于没有任何方法可以将参数限制为子类型或超类型,因此最简单地接受任何类型并回答<!>“no <!>”是最实用的。对于任何类型与集合类型无关的对象。
我总是认为这是因为remove()没有理由关心你给它的对象类型。无论如何,检查该对象是否是Collection包含的对象之一是很容易的,因为它可以对任何东西调用equals()。有必要检查add()上的类型以确保它只包含该类型的对象。
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来自异构的一组对象(即它包含字符串,数字,对象等)。你想要删除所有匹配,这是合法的,因为删除只会忽略非字符串,因为它们是不相等的。但如果你删除它(String o),那就不再有用了。