Do Wildcards in Java Generics Restrict or Increase Flexibility?

genericsjava

I have read many sources about wildcards and Java generics. Even though I have seen many explanations and answers, none of them seems correct.

The question is very simple: Do wilcards in Java generics increase flexibility?

My understand is "No". Wildcards in Java generics (wilcards) actually decrease flexibility.
When I first looked at and used Java generics mechanism, I rarely/never used wildcards. Very often I saw and used the form <T> abc(T xt). One day I got a test question about super and later I started looking at wildcards more closely.

Many sources and in particular: Effective Java 2nd gives an example as follows to show how wildcards increase flexibility.

public class Stack<E> {
    ...
    public void pushAll(List<E> list) {...}
    ...
}

The books goes on and says that since we should be able to use List of anything extending E as argument to pushAll(List<E> list), this is not flexible enough. Therefore, the book suggest changing the signature of the method to pushAll(List<? extends E> list). Now if E is Number then we can even use List<Integer> as argument for pushAll(List<? extends Number> list). And that we have increased flexibility (of the API).

My understanding is that the other solution is not to use wildcards but to use the regular typing. In that case, if we change the method signature to

public <T extends E> void pushAll(List<T> list)

we will achieve the same thing that the wildcard version wants to achieve.
And now in the body of the method, not only we can read from list, we can also write into it. If we use the the wildcard version, we cannot write into that list any value other than null. So in this view, wildcards actually decrease flexibility, not increase.

All in all, it looks to me that wildcards are actually used to decrease flexibility so that <? extends X> discourages people from writing into the object while <? super X> discourages people from reading the object (try it and you will get only Object as type of the returned value). The following example illustrates the <? super X> case.

public void popAll(List<? super E> list) {
    Object x = list.get(0) // Only get Object as type here, reading is discouraged
}

We can change the signature to public <T extends E> void popAll(List<T> list).
Now we can write or read as we want. There is no restriction.

So to sum up: All usages of wildcards can be substituted by typed parameter in the manner of <T extends E> (so there can be no need for the keyword super) . Using wildcards, we lose flexibility in the ability to both read and write into the object. Therefore, wildcards decrease flexibility.

Is it therefore true that wildcards in Java actually decrease flexibility? If not, how is my reasoning flawed?

[My question is flawed as per missing the cases as mentioned by @Doval in the comment under his answer.]

Best Answer

My understanding is that the other solution is not to use wildcards but to use the regular typing. In that case, if we change the method signature to (...) And now in the body of the method, not only we can read from list, we can also write into it. If we use the the wildcard version, we cannot write into that list any value other than null. So in this view, wildcards actually decrease flexibility, not increase.

The problem is that you're assuming the increased flexibility is for the person writing the function; it's for the person using the function. The more flexibility the implementation gives up, the more flexibility the caller gains. The restrictions on the implementation are guarantees for the caller.

Case in point, when you change pushAll(List<E>) to pushAll(List<? extends E>), the implementer can no longer assume that he knows the precise type stored in the list, and that is why you can't safely write to the list. But because of that, the caller is now free to to pass any list whose elements are a subtype of E, whereas before he could only pass lists of E.

You claim that if you change pushAll to <T extends E> void pushAll(List<T>) you can write to the list, but that's not true in this case. You don't know what class T is ahead of time, so you can't possibly create a T to insert into the list. The only way you could possibly add a T to the list is if the caller gave it to you. That's why Effective Java argues that you should replace type parameters with wildcards if they only appear once in the method; the main purpose of type parameters is to allow you to give an unknown type a name so you can refer to it more than once.

All in all, it looks to me that wildcards are actually used to decrease flexibility so that discourages people from writing into the object while <? super X> discourages people from reading the object (try it and you will get only Object as type of the returned value). (...) We can change the signature to public <T extends E> void popAll(List<T> list). Now we can write or read as we want. There is no restriction.

You can't just swap super for extends. Let's say you have a Stack<Number>. What's going to happen if you pass a List<Integer> to popAll and the stack tries to insert a Double into the list? Likewise, suppose you pass a List<Object> to pushAll; what's going to happen if the list has a String and you try to insert it into a stack of numbers? Bad things. That's why you use ? extends E when you want to read from the collection and ? super E when you plan on writing to it; it's the only safe way to do it. (There's a mnemonic for remembering which to use: PECS. Producer extends, consumer super.)

Besides, type parameters can't have lower bounds (you can't say T super SomeClass) so you're forced to use a wildcard for super.