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
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>)
topushAll(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 ofE
, whereas before he could only pass lists ofE
.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 classT
is ahead of time, so you can't possibly create aT
to insert into the list. The only way you could possibly add aT
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.You can't just swap
super
forextends
. Let's say you have aStack<Number>
. What's going to happen if you pass aList<Integer>
topopAll
and the stack tries to insert aDouble
into the list? Likewise, suppose you pass aList<Object>
topushAll
; what's going to happen if the list has aString
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 forsuper
.