Search code examples
javagenericscollectionssupercontravariance

Should remove(Object) be remove(? super E)


In this answer, I tried to explain why the Collection method add has the signature add(E) while remove is remove(Object). I came up with the idea that the correct signature should be

public boolean remove(? super E element)

And since this is invalid syntax in Java, they had to stick to Object, which just happens to be super E (supertype of E) for any E. The following code explains why this makes sense:

List<String> strings = new ArrayList();
strings.add("abc");

Object o = "abc"; // runtime type is String
strings.remove(o);

Since the runtime type is String, this succeeds. If the signature were remove(E), this would cause an error at compile-time but not at runtime, which makes no sense. However, the following should raise an error at compile time, because the operation is bound to fail because of its types, which are known at compile-time:

strings.remove(1);

The remove takes an Integer as an argument, which is not super String, which means it could never actually remove anything from the collection.

If the remove method was defined with the parameter type ? super E, situations like the above could be detected by the compiler.

Question:

Am I correct with my theory that remove should have a contravariant ? super E parameter instead of Object, so that type mismatches as shown in the above example can be filtered out by the compiler? And is it correct that the creators of the Java Collections Framework chose to use Object instead of ? super E because ? super E would cause a syntax error, and instead of complicating the generic system they simply agreed to use Object instead of super?

Also, should the signature for removeAll be

public boolean removeAll(Collection<? super E> collection)

Note that I do not want to know why the signature is not remove(E), which is asked and explained in this question. I want to know if remove should be contravariant (remove(? super E)), while remove(E) represents covariance.


One example where this does not work would be the following:

List<Number> nums = new ArrayList();
nums.add(1);
nums.remove(1); // fails here - Integer is not super Number

Rethinking my signature, it should actually allow sub- and supertypes of E.


Solution

  • This is a faulty assumption:

    because the operation is bound to fail because of its types, which are known at compile-time

    It's the same reasoning that .equals accepts an object: objects don't necessarily need to have the same class in order to be equal. Consider this example with different subtypes of List, as pointed out in the question @Joe linked:

    List<ArrayList<?>> arrayLists = new ArrayList<>();
    arrayLists.add(new ArrayList<>());
    
    LinkedList<?> emptyLinkedList = new LinkedList<>();
    arrayLists.remove(emptyLinkedList); // removes the empty ArrayList and returns true
    

    This would not be possible with the signature you proposed.