Java Generics: Upper Bounded and Lower Bounded Wildcards
I already took a look at the difference between Wildcards and Types in Java.
Now it’s time to take a closer look at Wildcards. Specifically, bounded and unbounded wildcards.
You may sometimes came across this when seeing either this signature parameter or error message:
List<? super Animal> animals
capture of ? extends Animal
Let’s see what this means.
Bounded Wildcards
Generally, bounded wildcards can be used to relax or restrict wildcard variables.
There are two of them: Lower Bounded Wildcards and Upper Bounded Wildcards.
Lower Bounded Wildcards
In the following method signature ? super Animal is a Lower Bounded Wildcard:
public List<? super Animal> lowerBoundedMethod(List<? super Animal> animals)
This restricts the unknown type ? to be a specific type or a super type of Animal.
In this example the method accepts any list that contains Animal or super types of Animal.
Input variables
Let’s assume that the class Dog looks like this: Dog extends Animal.
Then the following invocation of the lowerBoundedMethod is not allowed:
List<Dog> dogs = List.of(new Dog());
lowerBoundMethod(dogs);
The reason: List<Dog> is a specific typed List and not a subtype of List<? super Animal>.
To allow List<Dog> the type of List needs to be changed to: List<? super Animal> dogs = List.of(new Dog());.
Beware that dogs could also contain other classes than Dog! So, it is not strictly type-safe.
If specifically a List of Dog is needed, Upper Bounded methods are the better choice.
Within the Lower Bounded method
When the collection which is passed as an argument needs to be modified then Lower Bounded Wildcards are used.
But anyhow there are still some restrictions. Check the following code and see the comments below:
public List<? super Animal> lowerBoundedMethod(List<? super Animal> animals) {
animals.add(null); // (0)
animals.add(new Dog());
animals.add(new Cow()); // (1)
animals.set(0, animals.get(0)); // (2) capture of ? super Animal
animals.set(0, (Dog) animals.get(0)); // (3)
Dog dog = (Dog) animals.get(0); // (4)
Cow cow = (Cow) animals.get(0); // (5) capture of ? super Animal
Animal animal = animals.get(0); // (6)
return animals;
}
(0) - null is allowed.
(1) - A Cow is also an Animal. To restrict to Dog, List<? super Dog> dogs as parameter type needs to be used.
(2) - This isn’t allowed as it will lead to the capture of ? super Animal error. This error arises, because the compiler tries to infer the type of the object returned by animals.get(0).
But the compiler can’t infer the exact type of the returned object during the compile time, because the list contains elements with an unknown subtype due to the wildcard ? super Animal.
Consequently the compiler can’t assure that adding this object is allowed. So, it errors.
(3) - This compiles successfully as the compiler can assure that a sub-type of Animal is added. Though it may lead to a ClassCastException during the run time.
(4) + (5) - It’s possible to cast the instances of the list to their specific types. But other than done here a check for their type should be done first. Otherwise a ClassCastException can arise.
(6) - A cast is needed here. Otherwise it leads to the error capture of ? super Animal
To sum it up: Using Lower Bounded Wildcards like List<? super Animal> provides flexibility, but also requires extra caution and may lead to less type safety, as explicit type checks and casts need to be performed.
Upper Bounded Wildcard
In the following signature ? extends Animal is an Upper Bounded Wildcard:
public List<? extends Animal> upperBoundMethod(List<? extends Animal> animals)
This restricts the unknown type ? to be a specific type or a subtype type of Animal.
In this case extends means extends as in classes or implements as in interfaces.
Input variables
First, look at this List instantiation:
List<? extends Animal> upper = new ArrayList<>();
This means that the list contains an unknown subtype of Animal. But the compiler doesn’t know which.
Adding a Dog to the list is not possible, though it extends Animal.
To get a valid List it’s needed to declare it with a specific type of elements it contains:
List<Dog> upper = new ArrayList<>();
If an immutable List is ok, List.of can be used, because the compiler can infer the types and assure that the added elements are valid:
List<? extends Animal> upperExtends = List.of(
new Dog(),
new Cow()
);
Within the Lower Bounded method
Upper Bounded Wildcards allow to read in a type-safe manner from a collection. However, writing into it is restricted.
Check the following code and see the comments below:
public List<? extends Animal> upperBoundedMethod(List<? extends Animal> animals) {
animals.add(null); // (0)
animals.add(new Animal()); // (1)
animals.add(new Dog()); // (2) =>
animals.add(new Cow()); // (3) => => capture of ? extends Animal
animals.set(0, animals.get(0)); // (4) => capture of ? extends Animal
animals.set(0, (Dog) animals.get(0)); // (5) also not possible, read only!
Dog dog = (Dog) animals.get(0); // (6)
Cow cow = (Cow) animals.get(0); // (7)
Animal animal = animals.get(0); // (8)
return animals;
}
(0) - Adding null is allowed.
(1) to (5) - This leads to the capture of ? extends Animal error. The compiler can’t infer the specific subtype of the elements in animals during compile-time: animals could be a List<Dog>, a List<Cow>, or any other subtype of Animal.
Consequently, the compiler restricts adding any elements to the list.
(6) + (7) - It’s allowed to read from the list. Anyhow, it is needed to do a cast (and better an instanceof check to prevent ClassCastExceptions).
(8) - That’s allowed, too, as the compiler knows that the list contains at least an Animal.
To sum it up: Using Upper Bounded Wildcards like ? extends Animal restricts the access to a collection: It makes it possible to read elements from the list in a type safe manner, but restricts from adding into the list.
PECS
The described principle is also called PECS: Producer extends and Consumer super which can be broken down to:
- Use
? extendswhen you only get values out of a structure. - Use
? superwhen you only put values into a structure. - Don’t use a wildcard when you do both: get and put.
Conclusion
Upper Bounded and Lower Bounded Wildcards are used to relax/restrict access to data structures.
- Upper Bounded: Relaxes. Defined by:
? extends - Lower Bounded: Restricts. Defined by:
? super
This leads to PECS: Producer extends and Consumer super.