Java Generics: Upper Bounded and Lower Bounded Wildcards

Posted on Nov 16, 2023

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 ? extends when you only get values out of a structure.
  • Use ? super when 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.

Resources

Want to know more?

Keep on reading and choose one of the related articles. You can also check the home page for my latest thoughts, notes and articles.