Java 103 : Why do we need wildcards in java

Saurabh Kumar
5 min readApr 3, 2022

Let's digress a bit and know what wildcards are first

? is an unbounded wildcard in java. i.e it can hold anything that comes in its place, representing an unknown type.

e.g let's take an example where there is a simple utility function that is only responsible for checking if the size of the given container is ≤ a given threshold or not.
Instead of writing this utility function for all the containers and types, we can use wildcards instead. (actually there is a better way without wildcards but I am explaining wildcards :P )

public Boolean checkIfCorrectSize(List<?> input, Integer bound) {
return input.size() <= bound;
}

Just a small heads up before we dive deeper into this discussion,
1. Generic types are not co-variant !
which means its fine to add String object instances inside a List of Objects so the below example would work.
List<Object> list = new ArrayList<>();
list.add("DummyString")

but it's not allowed to assign a list of Strings to a list of Objects since they aren't related inherently.
List<Object> list = new ArrayList<String>();
-- compile time error

Let's take an example to understand wildcards better, (I stole it from effective Java though) where we want to create a generic stack of Numbers and support some operations.

public class Stack<E> { 
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}

All the operations are fairly simple to implement, we can use a generic list to implement all these operations.
What if we get a new requirement that we need to support one more operation pushAll() ? where we will get a sequence of elements that need to be pushed into the stack all at once?

we can think of implementing it like this

public void pushAll(List<E> src) {
for (E e : src)
push(e);
}

This implementation seems to work, but it is a bit more restrictive than it ideally should be. let me explain why !
This example below works, since Double is a subtype of Number and we can insert a Double instead of Number using the push function.

Stack<Number> numberStack = new Stack<>();
numberStack.push(0.3)

But what if we do something like this below? we get a compile-time error, why so? since we have already discussed generic types are not co-variant i.e
List<Double> is not a subtype of List<Number > even though, Double is a subtype of Number.

Stack<Number> numberStack = new Stack<>();
List<Double> doubleList = new ArrayList<>();
numberStack.pushAll(doubleList);
--- compile time error

So what can we do to make this API less restrictive? we can use wildcards !
we can change the declaration of the pushAll() method and get the work done

public void pushAll(List<? extends E> src) {
for (E e : src)
push(e);
}

<? extends E> read as any subtype of E, allows for passing in any List of subtype of the type E. but this benefit comes with a restriction as well, i.e now we cannot insert anything to this src parameter.
i.e we cannot do src.add(e) why so? since we actually don't know which subtype is inside this src List so we cannot insert anything inside it to help not corrupt this array.

Here is where a stupid ancronim comes in, PECS producer extends consumer super, It is supposed to make me remember the rules properly but it in turn confuse me !!

This acronym means that ? extends E always acts like a producer i.e we cannot insert anything to this type, only access elements from inside it.
Whereas ? super E always acts like a consumer i.e we can push elements into it but we cannot read from it. we will discuss more on this now.
I never really appreciated it, moreover it confuses me in certain scenarios I prefer going about it with the basics.

What if we get another requirement to support another function popAll() ?where we pass in a list and we need to add all the popped elements in this list so that we can do something with it later.

public void popAll(List<E> dst) { 
while (!isEmpty())
dst.add(pop());
}

Similar to the previous instance, this implementation with generic parameter is fine except it's a bit restrictive, let’s take this example, the Object is the super-type of every class hence it's correct to assign any instance to an object type variable.

Stack<Number> numberStack = new Stack<>();
Object object = numberStack.pop();
--- this compiles

but what if we try to pass in a list of any different type than Number ?
The code doesn't compile, but it's fine to do so as we have seen in the previous example.

Stack<Number> numberStack = new Stack<>(); 
List<Object> objects = new ArrayList<>();
numberStack.popAll(objects);
--- this doesn't compile

So to remove this deficiency in the API, we turn again to bounded wildcards.

public void popAll(List<? super E> dst) { 
while (!isEmpty())
dst.add(pop());
}

<? super E> read as any supertype of E, allows for passing in any List of super-type of the type E. Now we can add all the popped elements of type E to the dst list, but this benefit comes with another restriction as well, i.e now we cannot read anything from this dst parameter.
i.e we cannot read elements inside dst as an instance of type E

List<Number> obj = new ArrayList<>();
obj.add(0.3);

List<? super Number> list = obj;
Number foo = list.get(0);
^^ this wont compile
Object foo = list.get(0);
^^ this will compile

why so? since we actually don't know which supertype of E is inside this dst , so the best we can do is read it as an Object type instance.

Details to look out for!

  1. We discussed in detail the bounded wildcard parameters, what about the unbounded wildcard parameter ? is it used anywhere?
    ? is the unbounded wildcard used when we don't care what the type of the variable is, or when our implementation will be fine with the methods declared inside the Object class.
  2. What is really the difference between ? and generic type T ?
    There are subtle differences between both of them,
    Type parameter supports only upper bound i.e <T extends Foo>
    But <T super Foo> won't compile whereas wildcards support both upper and lower type bounds.
    Apart from these differences if I can really use either of them, then as a thumb rule I prefer using generic parameters only if there is a single generic parameter used, otherwise, I use the wildcards again since declaring multiple generic params looks messy.
  3. Difference between List<object>, List<?>, List<T>
    This should give me a fair idea of how good I am at writing, you should be able to reason this out, if I wrote this article well enough. There is a good explanation here if you want to verify if your understanding is correct !.

I will try to write a similar article about generics in kotlin, will update the link here soon ! /** WIP*/

leave me a clap if you found this article insightful 😄, Happy Reading !

--

--