为什么 Java 集合的删除方法不是通用的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/104799/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 11:03:22  来源:igfitidea点击:

Why aren't Java Collections remove methods generic?

javaapigenericscollections

提问by Chris Mazzola

Why isn't Collection.remove(Object o)generic?

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

Seems like Collection<E>could have boolean remove(E o);

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

Then, when you accidentally try to remove (for example) Set<String>instead of each individual String from a Collection<String>, it would be a compile time error instead of a debugging problem later.

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

采纳答案by dmeister

Josh Bloch and Bill Pugh refer to this issue in Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone, and Revenge of The Shift.

Josh Bloch 和 Bill Pugh 在Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone, and Revenge of The Shift 中提到了这个问题。

Josh Bloch says (6:41) that they attempted to generify the get method of Map, remove method and some other, but "it simply didn't work".

Josh Bloch 说 (6:41) 他们试图对 Map 的 get 方法、remove 方法和其他一些方法进行泛化,但“它根本不起作用”。

There are too many reasonable programs that could not be generified if you only allow the generic type of the collection as parameter type. The example given by him is an intersection of a Listof Numbers and a Listof Longs.

如果只允许集合的泛型类型作为参数类型,有太多合理的程序无法泛型。由他给出的例子是一个交叉点ListNumberS和 ListLong

回答by newacct

remove()(in Mapas well as in Collection) is not generic because you should be able to pass in any type of object to remove(). The object removed does not have to be the same type as the object that you pass in to remove(); it only requires that they be equal. From the specification of remove(), remove(o)removes the object esuch that (o==null ? e==null : o.equals(e))is true. Note that there is nothing requiring oand eto be the same type. This follows from the fact that the equals()method takes in an Objectas parameter, not just the same type as the object.

remove()(inMap和 in Collection)不是通用的,因为您应该能够将任何类型的对象传递给remove(). 删除的对象不必与您传入的对象类型相同remove();它只要求它们相等。从本说明书中remove()remove(o)将删除对象e,使得(o==null ? e==null : o.equals(e))true。请注意,没有什么要求oe是相同的类型。这是因为该equals()方法接受一个Objectas 参数,而不仅仅是与对象的类型相同。

Although, it may be commonly true that many classes have equals()defined so that its objects can only be equal to objects of its own class, that is certainly not always the case. For example, the specification for List.equals()says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So coming back to the example in this question, it is possible to have a Map<ArrayList, Something>and for me to call remove()with a LinkedListas argument, and it should remove the key which is a list with the same contents. This would not be possible if remove()were generic and restricted its argument type.

虽然,许多类已经equals()定义为使其对象只能等于其自己类的对象,这可能是普遍的事实,但情况并非总是如此。例如,规范中List.equals()说如果两个 List 对象都是 List 并且具有相同的内容,则它们是相等的,即使它们是 的不同实现List。因此,回到这个问题中的示例,Map<ArrayList, Something>我可以remove()使用 aLinkedList和作为参数进行调用,并且它应该删除具有相同内容的列表的键。如果remove()是泛型并限制其参数类型,则这是不可能的。

回答by Bob Gettys

Because if your type parameter is a wildcard, you can't use a generic remove method.

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

I seem to recall running into this question with Map's get(Object) method. The get method in this case isn't generic, though it should reasonably expect to be passed an object of the same type as the first type parameter. I realized that if you're passing around Maps with a wildcard as the first type parameter, then there's no way to get an element out of the Map with that method, if that argument was generic. Wildcard arguments can't really be satisfied, because the compiler can't guarantee that the type is correct. I speculate that the reason add is generic is that you're expected to guarantee that the type is correct before adding it to the collection. However, when removing an object, if the type is incorrect then it won't match anything anyway. If the argument were a wildcard the method would simply be unusable, even though you may have an object which you can GUARANTEE belongs to that collection, because you just got a reference to it in the previous line....

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

I probably didn't explain it very well, but it seems logical enough to me.

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

回答by Hosam Aly

In addition to the other answers, there is another reason why the method should accept an Object, which is predicates. Consider the following sample:

除了其他答案之外,该方法应该接受 an 的另一个原因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);
        }});
    }
}

The point is that the object being passed to the removemethod is responsible for defining the equalsmethod. Building predicates becomes very simple this way.

关键是传递给remove方法的对象负责定义equals方法。通过这种方式构建谓词变得非常简单。

回答by supercat

Assume one has a collection of Cat, and some object references of types Animal, Cat, SiameseCat, and Dog. Asking the collection whether it contains the object referred to by the Cator SiameseCatreference seems reasonable. Asking whether it contains the object referred to by the Animalreference may seem dodgy, but it's still perfectly reasonable. The object in question might, after all, be a Cat, and might appear in the collection.

假设一个人的集合Cat,和一些类型的对象引用AnimalCatSiameseCat,和Dog。询问集合是否包含由CatSiameseCat引用引用的对象似乎是合理的。询问它是否包含Animal引用所引用的对象可能看起来很狡猾,但这仍然是完全合理的。毕竟,所讨论的对象可能是 a Cat,并且可能出现在集合中。

Further, even if the object happens to be something other than a Cat, there's no problem saying whether it appears in the collection--simply answer "no, it doesn't". A "lookup-style" collection of some type should be able to meaningfully accept reference of any supertype and determine whether the object exists within the collection. If the passed-in object reference is of an unrelated type, there's no way the collection could possibly contain it, so the query is in some sense not meaningful (it will always answer "no"). Nonetheless, since there isn't any way to restrict parameters to being subtypes or supertypes, it's most practical to simply accept any type and answer "no" for any objects whose type is unrelated to that of the collection.

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

回答by ColinD

I always figured this was because remove() has no reason to care what type of object you give it. It's easy enough, regardless, to check if that object is one of the ones the Collection contains, since it can call equals() on anything. It's necessary to check type on add() to ensure that it only contains objects of that type.

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

回答by Stefan Feuerhahn

It was a compromise. Both approaches have their advantage:

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

  • remove(Object o)
    • is more flexible. For example it allows to iterate through a list of numbers and remove them from a list of longs.
    • code that uses this flexibility can be more easily generified
  • remove(E e)brings more type safety to what most programs want to do by detecting subtle bugs at compile time, like mistakenly trying to remove an integer from a list of shorts.
  • remove(Object o)
    • 更灵活。例如,它允许遍历数字列表并将它们从 long 列表中删除。
    • 使用这种灵活性的代码可以更容易地泛化
  • remove(E e)通过在编译时检测细微的错误,为大多数程序想要做的事情带来更多的类型安全性,例如错误地尝试从 short 列表中删除一个整数。

Backwards compatibility was always a major goal when evolving the Java API, therefore remove(Object o) was chosen because it made generifying existing code easier. If backwards compatibility had NOT been an issue, I'm guessing the designers would have chosen remove(E e).

在开发 Java API 时,向后兼容性始终是一个主要目标,因此选择 remove(Object o) 是因为它使现有代码的泛化变得更加容易。如果向后兼容性不是问题,我猜设计者会选择 remove(E e)。

回答by Jeff C

Remove is not a generic method so that existing code using a non-generic collection will still compile and still have the same behavior.

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

See http://www.ibm.com/developerworks/java/library/j-jtp01255.htmlfor details.

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

Edit: A commenter asks why the add method is generic. [...removed my explanation...] Second commenter answered the question from firebird84 much better than me.

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

回答by Patrick

Another reason is because of interfaces. Here is an example to show it :

另一个原因是因为接口。这是一个显示它的示例:

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

回答by noah

Because it would break existing (pre-Java5) code. e.g.,

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

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

Now you might say the above code is wrong, but suppose that o came from a heterogeneous set of objects (i.e., it contained strings, number, objects, etc.). You want to remove all the matches, which was legal because remove would just ignore the non-strings because they were non-equal. But if you make it remove(String o), that no longer works.

现在您可能会说上面的代码是错误的,但假设 o 来自一组异构的对象(即,它包含字符串、数字、对象等)。您想删除所有匹配项,这是合法的,因为 remove 只会忽略非字符串,因为它们不相等。但是如果你让它 remove(String o),那将不再有效。