Bounded Wildcards

We can use wildcard ? with either upper or lower bound.

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 Food and its subclasses and for change list parameter as below.

    public void display(List<? extends Food> list) {
        // display the list elements
    }

    display(new ArrayList<Food>());         
    display(new ArrayList<Snack>());      
    display(new ArrayList<Cookie>());
    display(new ArrayList<Fruit>());      
    display(new ArrayList<Apple>());      

    display(new ArrayList<String>());    // error
    display(new ArrayList<Integer>());   // error         

Now display() accepts only the list of Food and its subtypes - Snack, Cookie, Fruit, Apple and nothing else. Compiler throws error if we tries to pass list of String or Integer as they are not subclasses of Food.

We can use extends in variable declarations too as show below:

    List<Fruit> fruits = new ArrayList<>();
    List<Cookie> cookies = new ArrayList<>();
    List<Apple> apples = new ArrayList<>();

    List<? extends Food> f = fruits;
    List<? extends Snack> s = cookies;        
    List<? extends Apple> a = apples;

Various extends for our example class hierarchy.

  • List<? extends Food>   allows list of Food, Snack, Cookie, Fruit and Apple
  • List<? extends Snack>   allows list of Snack and Cookie
  • List<? extends Cookie>   allows list of Cookie
  • List<? extends Fruit>   allows list of Fruit and Apple
  • List<? extends Apple>   allows list of Apple

To sum up, extends allows type and its subtypes.

Super

The <? super Type> is a wildcard with lower bound. Suppose, we want a parameter that allows Food, Snack and Cookie. As we have seen earlier, the List<? extends Food> allows not only list of Food, Snack and Cookie but also list of Fruit and Apple, whereas the List<? extends Snack> allows only list of Snack and Cookie but not the list of Food. So top-down extends doesn’t work and we should use bottom-up super keyword.

    public void display(List<? super Cookie> list) {
        // display the list elements
    }

    display(new ArrayList<Food>());         
    display(new ArrayList<Snack>());      
    display(new ArrayList<Cookie>());

    display(new ArrayList<Fruit>());      // error
    display(new ArrayList<Apple>());      // error

The List<? super Cookie> allows list of Cookie and all its supertypes i.e. list of Snack and Food which is exactly what we want.

As usual, we can use super in variable declarations also bellow:

    
    List<Food> foods = new ArrayList<>();
    List<Fruit> fruits = new ArrayList<>();

    List<? super Food> f = foods;
    List<? super Snack> s = foods;        
    List<? super Apple> a = fruits;

Various super for our example class hierarchy.

  • List<? super Food>   allows list of Food
  • List<? extends Snack>   allows list of Snack and Food
  • List<? extends Cookie>   allows list of Cookie, Snack and Food
  • List<? extends Fruit>   allows list of Fruit and Food
  • List<? extends Apple>   allows list of Apple, Fruit and Food

To sum up, super allows type and its supertypes.

Producer and Consumer

So far, we saw how wildcards affects types, sub types and super types. Wildcards also impacts the methods; and to understand it, we group methods into two groups.

public interface List<E> {

    public E get(int index);        // method without formal type parameter
    public boolean add(E element);  // method with formal type parameter

}

The methods such as E get(int index) or E remove​(int index) has 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

The other group is methods that have one or more formal type parameters. 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 add methods. Some examples of add methods are shown below:

Method Group Reason
void add​(int index, E element) add parameter has E (type parameter)
boolean add(​E element) add parameter has E
boolean addAll​(Collection<? extends E> c) add parameter has E
E set​(int index, E element) add parameter has E

Next, we can group parameterized type into two groups - producer and consumer. When we get an item from list then list 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.

    List<Snack> snacks = new ArrayList<>();          // acts both as producer and consumer    
    List<?> unboundedVar = snacks;                   // acts as producer 
    List<? extends Snacks> upperBoundedVar = snacks; // acts as producer
    List<? super Snacks> lowerBoundedVar = snacks;   // acts both as producer and consumer

There is an acronym PECS to remember this, which is abbreviation for Producer extends, Consumer super.

  • assign list to List<? extends Snack>; it becomes producer.
  • assign the list to List<? super Snack>; it becomes consumer.

There are some rules about invoking add and get groups of methods on generic variables.

    
    List<Snack> snacks = new ArrayList<>();         // can call add and get methods
    List<?> unboundVar = snacks;                    // can call get methods
    List<? extends Snack> upperboundVar = snacks;   // can call get methods
    List<? super Snack> lowerboundVar = snacks;     // can call add and get methods

    // can call get group of methods on all
    snacks.get(0);
    unboundVar.get(0);
    upperboundVar.get(0);
    lowerboundVar.get(0);

    // can call add group of methods only on consumers
    snacks.add(new Snack());
    lowerboundVar.add(new Snack());
    unboundVar.add(new Snack());        // error
    upperboundVar.add(new Snack());     // error
    

All generic variables can act as producers i.e. we can call get group of methods on all of them. But List<?> and List<? extends Snacks> can’t act as consumers, that means we can’t call add group of 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 any type of variables.
  • can’t invoke add methods (methods with one or more type parameter) on variables with wildcard (List<?> etc.,) and wildcard with super (List<? extends Snack> etc.,).

Summary

  • List<?> accepts list of any type.
  • List<? extends T> accepts list of type and all its subtypes.
  • List<? super T> accepts list of type and all its supertypes.
  • 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 variables.
  • can’t invoke add methods (methods with one or more type parameter) on variables with wildcard (List<?> etc.,) and wildcard with super (List<? extends Snack> etc.,).