为什么 Java 集合不移除泛型方法?

为什么 Remove (Object o)不是通用的?

看起来 Collection<E>可能有 boolean remove(E o);

然后,当您意外地尝试从 Collection<String>中删除(例如) Set<String>而不是每个字符串时,这将是编译时错误,而不是稍后的调试问题。

19564 次浏览

因为如果类型参数是通配符,则不能使用泛型删除方法。

我似乎记得在使用 Map 的 get (Object)方法时遇到过这个问题。在这种情况下,get 方法不是泛型的,尽管它应该被合理地期望传递一个与第一个类型参数类型相同的对象。我意识到,如果使用通配符作为第一个类型参数传递 Map,那么如果该参数是泛型的,那么就没有办法使用该方法从 Map 中获取元素。实际上不能满足通配符参数,因为编译器不能保证类型是正确的。我推测 add 是泛型的原因是您希望在将其添加到集合之前保证类型是正确的。但是,当删除一个对象时,如果类型不正确,那么它无论如何都不会匹配任何对象。如果这个参数是一个通配符,那么这个方法就是不可用的,即使你有一个对象,你可以保证它属于这个集合,因为你刚刚在前一行得到了一个对它的引用..。

我可能没有很好地解释清楚,但对我来说,这似乎足够合乎逻辑。

“移除”不是泛型方法,因此使用非泛型集合的现有代码仍将编译并具有相同的行为。

详情请参阅 http://www.ibm.com/developerworks/java/library/j-jtp01255.html

编辑: 评论者会问为什么 add 方法是泛型的。[ ... 删除了我的解释... ]第二个评论者回答了火鸟84的问题,比我好多了。

因为它会破坏现有的(Java5之前的)代码,

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

现在您可能会说上面的代码是错误的,但是假设 o 来自一个异构的对象集(即,它包含字符串、数字、对象等)。您希望删除所有匹配项,这是合法的,因为 delete 会忽略非字符串,因为它们是不相等的。但是如果将其移除(String o) ,则不再起作用。

我一直认为这是因为 remove ()没有理由关心你给它什么类型的对象。无论如何,检查该对象是否是 Collection 包含的对象之一都很容易,因为它可以对任何对象调用 equals ()。有必要检查 add ()上的类型,以确保它只包含该类型的对象。

Josh Bloch 和 Bill Pugh 在 < a href = “ https://www.youtube.com/watch? v = wDN _ EYUvUq0”rel = “ noReferrer”> < em > Java Puzzlers IV: The 幻影参考威胁,克隆人的攻击,和复仇 移位。

Josh Bloch 说(6:41)他们试图通用 get 方法 移除方法和其他一些方法,但是“它根本不工作”。

有太多合理的程序不能泛化 只允许将集合的泛型类型作为参数类型。 他给出的例子是 NumberListLongList

除了其他的答案,还有另一个原因为什么方法应该接受一个 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方法。这样构建谓词就变得非常简单。

remove()(在 MapCollection中)不是泛型的,因为您应该能够将任何类型的对象传递给 remove()。被移除的对象不必与传递给 remove()的对象的类型相同; 它只要求它们相等。从 remove()的规范中,remove(o)删除对象 e,使得 (o==null ? e==null : o.equals(e))true。请注意,没有任何东西要求 Map0和 e是相同的类型。这是因为 Map2方法接受 Map3作为参数,而不仅仅是与对象相同的类型。

虽然,很多类都定义了 equals(),因此它的对象只能等于它自己类的对象,这种情况通常是正确的,但事实并非总是如此。例如,List.equals()的规范指出,如果两个 List 对象都是 List 并且具有相同的内容,则它们是相等的,即使它们是 List的不同实现。回到这个问题中的例子,可以有一个 Map<ArrayList, Something>,我可以用一个 LinkedList作为参数调用 remove(),它应该移除一个相同内容的列表的键。如果 remove()是泛型的并且限制了它的参数类型,那么这是不可能的。

假设有一个 Cat的集合,以及一些类型为 AnimalCatSiameseCatDog的对象引用。询问集合是否包含 CatSiameseCat引用所引用的对象似乎是合理的。询问它是否包含 Animal引用所引用的对象可能看起来有问题,但它仍然是完全合理的。毕竟,有问题的对象可能是 Cat,并且可能出现在集合中。

此外,即使对象碰巧不是 Cat,也不用担心它是否出现在集合中——只需回答“不,它不出现”。某种类型的“查找样式”集合应该能够有意义地接受任何超类型的引用,并确定该对象是否存在于集合中。如果传入的对象引用是一个不相关的类型,那么集合就不可能包含它,因此查询在某种意义上是没有意义的(它总是回答“ no”)。尽管如此,由于没有任何方法将参数限制为子类型或超类型,因此最实际的做法是简单地接受任何类型,并对与集合类型无关的任何对象回答“否”。

另一个原因是接口,下面的例子可以说明这一点:

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 */
}

这是一种妥协,两种方法各有优势:

    • 更灵活。例如,它允许迭代一个数字列表并从一个长列表中删除它们。
    • 使用这种灵活性的代码可以更容易地生成
  • remove(E e)通过在编译时检测微小的错误(比如错误地试图从短片列表中删除一个整数) ,为大多数程序想要做的事情带来了更多的类型安全性。

向后兼容性一直是演化 JavaAPI 时的一个主要目标,因此选择 delete (Object o)是因为它使生成现有代码更加容易。如果向后兼容性不是一个问题,我猜测设计师会选择删除(E e)。