views:

2069

answers:

7

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

Seems like Collection<E> could have 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.

+5  A: 

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

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....

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

firebird84
Could you elaborate on this a little?
Thomas Owens
A: 

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

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

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

Jeff C
Then why is the add method generic?
firebird84
@firebird84 remove(Object) ignores objects of the wrong type, but remove(E) would cause a compile error. That would change the behavior.
noah
:shrug: -- runtime behavior is *not* changed; compile error is not *runtime* behavior. The add method's "behavior" changes in this way.
Jason S
A: 

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

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.

noah
If I instantiate a List<String> I would expect to only be able to call List.remove(someString); If I need to support backward compatibility, I would use a raw List -- List<?> then I can call list.remove(someObject), no?
Chris Mazzola
If you replace "remove" with "add" then that code is just as broken by what was actually done in Java 5.
DJClayworth
+2  A: 

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.

ColinD
This is what I was going to say.
Jay R.
+11  A: 

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" (Google TechTalk).

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". 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 List of Numbers and a List of Longs.

dmeister
Why add() can take a typed parameter but remove() can't is still a bit beyond my comprehension. Josh Bloch would be the definitive reference for Collections questions. It might be all I get without trying to make a similar collection implementation and see for myself. :( Thanks.
Chris Mazzola
Chris - read the Java Generics Tutorial PDF, it will explain why.
JeeBee
Actually, it's very simple! If add() took a wrong object, it would break the collection. It would contain things it's not supposed to! That is not the case for remove(), or contains().
Kevin Bourrillion
Incidentally, that basic rule -- using type parameters to prevent actual damage to the collection only -- is followed absolutely consistently in the whole library.
Kevin Bourrillion
+2  A: 

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

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) {
        ArrayList<Person> list = new ArrayList<Employee>();
        // ...

        // to remove the first employee with a specific name:
        list.remove(new Person(someName1));

        // to remove the first developer that matches some criteria:
        list.remove(new Developer(someName2, someCompany, 10));

        // to remove the first employee who is either
        // a developer or an employee of someCompany:
        list.remove(new Object() {
            public boolean equals(Object employee) {
                return (employee instanceof Developer
                     || employee.company == someCompany);
            }
        }

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

Hosam Aly
Investor? (Filler filler filler)
Matt R
The last is not a great idea: how is list.remove() implemented -- developer.equals(yourAnonymousObject) or yourAnonymousObject.equals(developer)?
Matt R
The list is implemented as `yourObject.equals(developer)`, as documented in the Collections API: http://java.sun.com/javase/6/docs/api/java/util/Collection.html#remove(java.lang.Object)]
Hosam Aly
+8  A: 

remove() (in Map as 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 e such that (o==null ? e==null : o.equals(e)) is true. Note that there is nothing requiring o and e to be the same type. This follows from the fact that the equals() method takes in an Object as parameter, not just the same type as the object.

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 LinkedList as 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.

newacct
*"`remove(e)` removes the object `o` such that `(o == null ? e == null : o.equals(e)` is true."* **That's wrong.** It's exactly the opposite of what `remove(e)` does. As in the javadocs (http://java.sun.com/javase/6/docs/api/java/util/Collection.html#remove%28java.lang.Object%29), `remove(` ** `o` ** `)` removes the object **`e`** such that the condition you described is true. This is very important, because it allows the object being passed to have the upper hand deciding what is to be removed. Check my answer (http://stackoverflow.com/questions/104799/483016#483016) for a sample.
Hosam Aly
okay its fixed .
newacct