Bounded Wildcards
The previous tutorial covered unbounded wildcard, and now we learn to restrict the wildcards with extends (upper bound) or super (lower bound).
This tutorial uses following class hierarchy:
Extends
The <? extends Type>
is a wildcard with upper bound. Extends allows us to restrict the unbounded List<?>
to some type and its subtypes. For example, we want restrict display method only to Pet and its sub classes,
public void display(List<? extends Pet> list) {
// display the list elements
}
display(new ArrayList<Pet>());
display(new ArrayList<Dog>());
display(new ArrayList<Bulldog>());
display(new ArrayList<Cat>());
display(new ArrayList<Persian>());
// String and Integer are not sub classes of Pet
display(new ArrayList<String>()); // error
display(new ArrayList<Integer>()); // error
The display(List<? extends Pet> list)
accepts only the list of Pet, Dog, Bulldog, Cat, Persian and nothing else. Compiler throws error if we try to pass list of String or Integer as they are not sub classes of Pet.
We can also use extends in variable declarations as shown below:
List<Cat> cats = new ArrayList<>();
List<Bulldog> bulldogs = new ArrayList<>();
List<Persian> persians = new ArrayList<>();
List<? extends Pet> p = cats;
List<? extends Dog> d = bulldogs;
List<? extends Persian> p = persians;
Various extends and allowed lists for our example Pet hierarchy are,
- List<? extends Pet> :
List<Pet>, List<Dog>, List<Cat>, List<Bulldog> or List<Persian>
- List<? extends Dog> :
List<Dog> or List<Bulldog>
- List<? extends Bulldog> :
List<Bulldog>
- List<? extends Cat> :
List<Cat> or List<Persian>
- List<? extends Persian> :
List<Persian>
To sum up, extends allows type and its subtypes.
Super
The <? super Type>
is the wildcard with lower bound. Suppose, we want a parameter that allows Pet, Dog and Bulldog. As we have seen earlier, the List<? extends Pet>
allows not only the list of Pet, Dog and Bulldog but also Cat and Persian, whereas the List<? extends Dog>
allows only list of Dog and Bulldog but not the list of Pet. So top-down extends doesn’t fit the bill and let’s try bottom-up super keyword.
public void display(List<? super Bulldog> list) {
// display the list elements
}
display(new ArrayList<Pet>());
display(new ArrayList<Dog>());
display(new ArrayList<Bulldog>());
// Cat and Persian are not super classes of Bulldog
display(new ArrayList<Cat>()); // error
display(new ArrayList<Persian>()); // error
The List<? super Bulldog>
allows list of Bulldog and all its super types i.e. list of Dog and Pet which is exactly what we want. The list of Cat and Persian are not allowed as they are not super classes of Bulldog.
As usual, we can use super in variable declarations as bellow:
List<Pet> pets = new ArrayList<>();
List<? super Pet> petList = pets;
List<? super Dog> dogList = pets;
List<Cat> cats = new ArrayList<>();
List<? super Persian> persianList = cats;
Various supers and allowed lists for our example Pet hierarchy are,
- List<? super Pet> :
List<Pet>
- List<? super Dog> :
List<Dog> or List<Pet>
- List<? super Bulldog> :
List<Bulldog> or List<Dog> or List<Pet>
- List<? super Cat> :
List<Cat> or List<Pet>
- List<? super Persian> :
List<Persian> or List<Cat> or List<Pet>
To sum up, super allows type and its super types.
Producer and Consumer
So far, we saw how wildcards sets upper or lower bounds of variables and method parameters. In addition to that, wildcards also restricts the methods can be invoked and to understand that, we categorize parameterized types based on their behavior - producers and consumers.
When we get an item from list then it acts as producer and when an item is added then it acts as consumer. Let’s see how a list changes its nature when assigned to unbounded, upper bounded or lower bounded wildcards variables.
// acts both as both as producer and consumer
List<Dog> dogs = new ArrayList<>();
// wildDogs act as producer
List<?> wildDogs = dogs;
// extendsDogs variable acts as producer
List<? extends Dogs> extendsDogs = dogs;
// superDogs variable acts both as producer and consumer
List<? super Dogs> superDogs = dogs;
The same instance of List<Dog>
changes its nature when we assign it different wildcard variables. To remember this, use acronym PECS which is abbreviation for Producer extends, Consumer super.
List<? extends Dog>
- producer extends; it is a producerList<? super Dog>
consumer super; it is a consumer (and, also as producer)
To sum up, all can act as producers, but plain parameterized type List<Dog>
and wildcard with lower bound List<? super Dog>
can also act as consumers.
Gets and Puts
Next let’s see which of the methods that can be invoked by the producers and consumers. To understand that we need to divide methods of generic type into two groups - get and put methods.
public interface List<E> {
// method without formal type parameter (no E)
public E get(int index);
public boolean remove(Object o)
public void clear()
// methods with formal type parameter (has E)
public boolean add(E element);
public void add(int index, E element)
}
The methods such as E get(int index)
and void clear()
have no formal type parameter with E. We can ignore the return type even if it is type parameter. Similarly, boolean remove(Object o)
is also a method with no formal type parameter with E. For convenience, we call all such methods as get methods. Some more examples are:
Method | Group | Reason |
---|---|---|
E get(int index) | get | no E in parameter, even though return type is E |
E remove(int index) | get | no E in parameter, even though return type is E |
boolean remove(Object o) | get | no E in parameter |
boolean removeAll(Collection<?> c) | get | no E in parameter |
boolean retainAll(Collection<?> c) | get | no E in parameter |
void clear() | get | no E in parameter |
The other group consists of methods that have one or more formal type parameter. For example, the method E set(int index, E element)
and void add(E element)
have formal type parameter element
whose type is E. Again informally, we call all such methods as put methods. Some examples of put methods are shown below:
Method | Group | Reason |
---|---|---|
void add(int index, E element) | put | parameter has E (type parameter) |
boolean add(E element) | put | parameter has E |
boolean addAll(Collection<? extends E> c) | put | parameter has E |
E set(int index, E element) | put | parameter has E |
Note that the names, put and get, are misnomers; they are named as such just for convenience. For example the void clear()
doesn’t get anything but still grouped under get method as it has no formal type parameter E. Similarly, a method may not put, set or add any item but still grouped as put as long as it has one or more formal type parameter E. In the following example, the element E not added or put but used to get its hash and still, it is a put method.
// method doen't do a put/add operation but it is a put method!
public int getItemHash(E element) {
return element.hashcode();
}
Invocation Rules
Now, let’s know the rules about invoking methods (get and put) on plain, unbounded and bounded types (consumers and producers).
// no wildcard, acts as producer and consumer
// can call get and put methods
List<Dog> dogs = new ArrayList<>();
// unbound, acts as producer
// can call only the get methods
List<?> unbounded = dogs;
// upper bound, acts as producer
// can call only the get methods
List<? extends Dog> producer = dogs;
// lower bound, acts as producer and consumer
// can call get and put methods
List<? super Dog> consumer = dogs;
// can call get methods on all the above
dogs.get(0);
unbounded.get(0);
producer.get(0);
consumer.get(0);
// can call put methods only on consumers
dogs.add(new Dog());
unbounded.add(new Dog()); // error
producer.add(new Dog()); // error
consumer.add(new Dog());
All generic variables can act as producers i.e. we can call get methods on all of them. But List<?>
and List<? extends Dogs>
can’t act as consumers, that means we can’t call put methods (methods with one or more type parameters) on them.
Yet another way to remember is
- can call get methods (methods without type parameter) on all type of generic variables (
List<Pet>
,List<?>
,List<? extends Pet>
andList<? super Dog>
). - can invoke put methods (methods with one or more type parameter) only on variables without wildcard (
List<Pet>
etc.,) and wildcard with super (List<? super Dog>
etc.,).
A Twist
Above, we told that List<? super Dog>
acts as consumer and call put methods on it. Now, consider this,
List<Pets> pets = new ArrayList<>();
pets.add(new Pet());
pets.add(new Dog());
pets.add(new Bulldog());
List<? super Dog> superDog = pets;
superDog.add(new Pet()); // error
superDog.add(new Dog());
superDog.add(new Bulldog());
To List<Pet>
we can add Pet, Dog or Bulldog. But, why compiler is not allowing us to add a Pet to List<? super Dog>
even though it points to the same List<Pet>
. Reason being, when List<Pets>
is assigned to List<? super Dog>
, the compiler treats it as List<Dog>
which can hold only the Dog or Bulldog but not Pet.
Summary
List<?>
accepts list of any type.List<? extends T>
accepts list of type and all its sub types.List<? super T>
accepts list of type and all its super types.- PECS is acronym for Producer Extends Consumer Super which denotes that variable which uses extends act as producer whereas variable that use super is a consumer.
- can call get methods (methods without type parameter) on all type of generic variables.
- can’t invoke put methods (methods with one or more type parameter) on variables with wildcard (
List<?>
etc.,) and wildcard with extends (Producers -List<? extends Dog>
etc.,). - the put methods are allowed on variables without wildcard (
List<Pet>
etc.,) and wildcard with super (Consumer -List<? super Dog>
etc.,). - The compiler considers the
List<Pet>
assigned toList<? super Dog>
asList<Dog>
and allows to add only the Dog or Bulldog, but not the Pet.
In the next tutorial we will go through wildcards in Java API to reinforce the concepts learned here.