2

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.

Community
  • 1
  • 1
Clashsoft
  • 11,553
  • 5
  • 40
  • 79
  • possible duplicate of [Why aren't Java Collections remove methods generic?](http://stackoverflow.com/questions/104799/why-arent-java-collections-remove-methods-generic) – Joe Jun 11 '15 at 15:13
  • 1
    This is not a duplicate, as I want to know if the `remove` method should be contravariant, while the question is about why it is not `remove(E)` (which is covariant for `E` and subtypes). – Clashsoft Jun 11 '15 at 15:16
  • @tobias_k you can try that out... (it gives you a nice syntax error at `T super E`, because type variables can only use `extends`) – Clashsoft Jun 11 '15 at 15:17
  • See the answer here http://smallwig.blogspot.ru/2007/12/why-does-setcontains-take-object-not-e.html – AdamSkywalker Jun 11 '15 at 16:01

4 Answers4

4

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.

Community
  • 1
  • 1
blgt
  • 8,135
  • 1
  • 25
  • 28
  • "This would not be possible with the signature you proposed." **Why not simply make another overload for Collection?** – Anurag Awasthi Mar 29 '17 at 15:32
  • @AnuragAwasthi That doesn't solve the underlying issue. What if its not a collection but another arbitrary supertype? – blgt Mar 29 '17 at 19:43
3

remove(? super E) is entirely equivalent to remove(Object), because Object is itself a supertype of E, and all objects extend Object.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • Not entirely: `stringList.remove(1)` would fail with `? super E` because `Integer` is *not* `super String`. But as you can read in my note in the original question, `super` is not the perfect solution either. – Clashsoft Jun 11 '15 at 15:36
  • 3
    Nope. `1` is _also_ an `Object`, and `Object` is a supertype of `E`. – Louis Wasserman Jun 11 '15 at 15:37
  • 1
    Yes, yes, yes. I know that you could do `Object o = 1; stringList.remove(o)` and that would be perfectly fine with `Object super String`, but for `remove(1)`, the type of the parameter is clearly `Integer`. And this is a case that simply makes no sense as you can't remove an integer from a list of strings anyway, and thus it should be reported by the compiler. – Clashsoft Jun 11 '15 at 15:39
  • Also, can you call a `foo(List super String>)` with a `List` because 'Integer is also an Object'? That defeats the whole purpose of `super`. – Clashsoft Jun 11 '15 at 15:41
  • 3
    @Clashsoft: the compiler would disagree with you; all parameters can always be converted to their supertypes. – Louis Wasserman Jun 11 '15 at 15:41
  • 2
    @Clashsoft: no, because a `List` is not a subtype of `List`. – Louis Wasserman Jun 11 '15 at 15:41
  • The *current* compiler, which just happens to *not* support `? super E`. If that was a thing, it wouldn't let you call `stringList.remove(1)`. And this my friend is the whole purpose of my question. – Clashsoft Jun 11 '15 at 15:42
  • @Clashsoft `? super E` has an upper bound of `Object`, though. That is the way it works right now. Basically what you described in the question is not what the `super` bound does. Bounded wildcards are only needed in the first place because of the way generic subtyping works. – Radiodef Jun 11 '15 at 15:44
  • While I agree with you, `super` in my case is not really a generic bound. It simply means 'anything that has type E or a supertype of that'. In fact, `super` is not even correct here, as it should be `super` and `extends` at the same time (note in the question). 'has type E' implies 'E or subtypes of E'. – Clashsoft Jun 11 '15 at 15:46
  • "If that was a thing, it wouldn't let you call stringList.remove(1)." It absolutely would. – newacct Jun 11 '15 at 22:04
2

I think that designers of the collections framework made a decision to keep remove untyped, because it is a valid solution that lets you keep a post-condition without introducing a pre-condition or compromising type safety.

The post-condition of a c.remove(x) is that after the call x is not present in c. Method signature remove(Object) lets you pass any object or null, with no further checks. Method signature ? super E, on the other hand, introduces a pre-condition on the type of x, requiring it to be related to E.

Each pre-condition that you introduce in an API makes your API harder to use. If removing a pre-condition lets you keep all your post-conditions, it is a good idea to remove the pre-condition.

Note that removing an object of a wrong type is not necessarily an error. Here is a small example:

class Segregator {
    private final Set<Integer> ints = ...
    private final Set<String> strings = ...
    public void addAll(List<Object> data) {
        for (Object o : data) {
            if (o instanceof Integer) {
                ints.add((Integer)o);
            }
            if (o instanceof String) {
                strings.add((String)o);
            }
        }
    }
    // Here is the method that becomes easier to write:
    public void removeAll(List<Object> data) {
        for (Object o : data) {
            ints.remove(o);
            strings.remove(o);
        }
    }
}

Note how removeAll method's code is simpler than the code of addAll, because remove does not care about the type of the object that you pass to it.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Your `removeAll` method still works with `remove(? super E)`, since `? super Integer` *and* `? super String` is true for `Object o`. – Clashsoft Jun 11 '15 at 15:24
  • @Clashsoft I guess I could have picked unrelated classes for the illustration. My point was that it is OK to allow deletion of things that could not possibly have been inserted in the first place, because it makes use of your class simpler. – Sergey Kalinichenko Jun 11 '15 at 15:27
  • True, but also more 'dangerous' (in terms of potential programming errors that could be reported if `? super E` was a thing) – Clashsoft Jun 11 '15 at 15:28
0

In your question you already explained why it can't (or shouldn't) be remove(E).

But there is also a reason why it shouldn't be remove(? super E). Imagine some piece of code where you have an object of unknown type. You still might want to try to remove that object from that list. Consider this code:

public void removeFromList(Object o, Collection<String> col) {
    col.remove(o);
}

Now your argument was, that remove(? super E) is more typesafe way. But I say it doesn't have to be. Look at the Javadoc of remove(). It says:

More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this collection contains one or more such elements.

So all the preconditions the parameter has to match is that you can use == and equals() on it, which is the case with Object. This still enables you to try to remove an Integer from a Collection<String>. It just wouldn't do anything.

André Stannek
  • 7,773
  • 31
  • 52
  • Well, your example would work as `Object super String`. It is not about the fact that `remove` would fail with an incorrect type (it won't), but it would be possible to filter programming problems like `stringList.remove(1)` before they even arise, by allowing a nice compiler error because of incompatible types. – Clashsoft Jun 11 '15 at 15:30
  • @Clashsoft Allright, bad example. Still my last paragraph counts. Such a case could be accidental but you might be able to think of cases where this is on purpose. Gotta go for now, will try to find a better example later on. – André Stannek Jun 11 '15 at 15:33
  • In these cases you could cast to `Object` and everything would work again, as `Object super E` no matter what. – Clashsoft Jun 11 '15 at 15:43