为什么Java Collections不能通用删除方法?

时间:2020-03-06 14:27:28  来源:igfitidea点击:

为什么Collection.remove(Object o)不通用?

好像Collection <E>可能有boolean remove(E o);

然后,当我们不小心尝试从Set <String>中删除(例如)Set <String>`而不是每个单独的String时,这将是编译时错误,而不是以后的调试问题。

解决方案

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

我似乎想起了用Map的get(Object)方法遇到这个问题。在这种情况下,get方法不是通用的,尽管应该合理地期望将其传递给与第一个type参数相同类型的对象。我意识到,如果我们要传递带有通配符作为第一个类型参数的Maps,那么如果该参数是通用的,则无法使用该方法从Map中获取元素。不能真正满足通配符参数,因为编译器无法保证类型正确。我推测add是泛型的原因是,我们应该在将其添加到集合之前保证类型正确。但是,删除对象时,如果类型不正确,则无论如何都不会匹配任何内容。如果参数是通配符,则该方法将根本无法使用,即使我们可能拥有一个可以保证该对象属于该集合的对象,因为我们在上一行中仅得到了对它的引用。

我可能没有很好地解释它,但是对我来说似乎很合逻辑。

Remove不是通用方法,因此使用非通用集合的现有代码仍将编译且仍具有相同的行为。

有关详细信息,请参见http://www.ibm.com/developerworks/java/library/j-jtp01255.html。

编辑:评论者问为什么add方法是通用的。 [...删除了我的解释...]第二位评论者回答了firebird84的问题比我好得多。

因为它将破坏现有的(Java5之前的)代码。例如。,

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

现在我们可能会说上述代码是错误的,但是假设o来自不同种类的对象(即,它包含字符串,数字,对象等)。我们想删除所有匹配项,这是合法的,因为remove会忽略非字符串,因为它们不相等。但是,如果将其设置为remove(String o),它将不再起作用。

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

Josh Bloch和Bill Pugh在Java Puzzlers IV:The
幻影参考威胁,克隆的攻击和复仇
转移。

乔什·布洛赫(Josh Bloch)说(6:41)他们试图使get方法泛化
地图,删除方法和其他方法,但"它根本不起作用"。

如果有太多合理的程序无法生成
我们只允许将集合的通用类型作为参数类型。
他给出的示例是Number的List和a的交集。
s的名单`。

除了其他答案之外,还有另一个原因导致该方法应接受谓词"对象"。考虑以下示例:

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()(在Map中以及在Collection中)不是通用的,因为我们应该能够将任何类型的对象传递给remove()。删除的对象不必与传递给remove()的对象具有相同的类型;它只要求它们是平等的。从remove()的规范中,remove(o)删除对象e,使得(o == null?e == null:o.equals(e))true。注意,没有什么要求oe必须是同一类型。这是基于这样的事实,即equals()方法采用了"对象"作为参数,而不仅仅是对象的类型。

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