Skip to content

Extends Wildcard

We previously discussed the inheritance relationship of generics: Pair<Integer> is not a subclass of Pair<Number>.

Suppose we define Pair<T>:

java
public class Pair<T> { ... }

Then, we write a static method for the Pair<Number> type, which accepts a parameter of type Pair<Number>:

java
public class PairHelper {
    static int add(Pair<Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

The above code compiles correctly. When we use it, we pass:

java
int sum = PairHelper.add(new Pair<Number>(1, 2));

Note: The input type is Pair<Number>, and the actual parameter type is (Integer, Integer).

Since the actual parameter is of type Integer, let's try passing Pair<Integer>:

java
public class Main {
    public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        int n = add(p);
        System.out.println(n);
    }

    static int add(Pair<Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

If you run this directly, you'll get a compilation error:

incompatible types: Pair<Integer> cannot be converted to Pair<Number>

The reason is clear: Pair<Integer> is not a subclass of Pair<Number>, so add(Pair<Number>) does not accept the parameter type Pair<Integer>.

However, the code in the add() method shows that passing Pair<Integer> aligns perfectly with the internal type specifications, as the statements:

java
Number first = p.getFirst();
Number last = p.getLast();

result in actual types being Integer while the reference type is Number, which is fine. The issue lies in the method parameter type being fixed to only accept Pair<Number>.

Is there a way to allow the method parameter to accept Pair<Integer>? Yes, by using Pair<? extends Number>, which enables the method to accept any generic type that is Number or a subclass of Number. We can rewrite the code as follows:

java
public class Main {
    public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        int n = add(p);
        System.out.println(n);
    }

    static int add(Pair<? extends Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

Now, passing Pair<Integer> to the method aligns with the parameter type Pair<? extends Number>. This use of <? extends Number> is referred to as an upper bounds wildcard, which limits the generic type T to be a subclass of Number.

In addition to being able to pass Pair<Integer>, we can also pass Pair<Double>, Pair<BigDecimal>, etc., since both Double and BigDecimal are subclasses of Number.

If we examine the getFirst() method for Pair<? extends Number>, the actual method signature becomes:

java
<? extends Number> getFirst();

Thus, the return value can be Number or a subclass of Number, allowing it to be safely assigned to a variable of type Number:

java
Number x = p.getFirst();

However, we cannot predict that the actual type will be Integer, so the following code will not compile:

java
Integer x = p.getFirst();

This is because the actual return type could be Integer, Double, or some other type; the compiler can only confirm that the type is definitely a subclass of Number (including Number itself), but the specific type cannot be determined.

Now let’s examine the set method for Pair<T>:

java
public class Main {
    public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        int n = add(p);
        System.out.println(n);
    }

    static int add(Pair<? extends Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        p.setFirst(new Integer(first.intValue() + 100));
        p.setLast(new Integer(last.intValue() + 100));
        return p.getFirst().intValue() + p.getFirst().intValue();
    }
}

class Pair<T> {
    private T first;
    private T last;

    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }

    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
    public void setFirst(T first) {
        this.first = first;
    }
    public void setLast(T last) {
        this.last = last;
    }
}

As expected, we will encounter a compilation error:

incompatible types: Integer cannot be converted to CAP#1
where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number

The compilation error occurs because the argument passed to p.setFirst() is of type Integer. Some might wonder, since p is defined as Pair<? extends Number>, why can't setFirst(? extends Number) accept an Integer?

The reason lies in type erasure. If the p we pass is Pair<Double>, it satisfies the parameter definition Pair<? extends Number>, yet Pair<Double>’s setFirst() cannot accept an Integer.

This highlights an important limitation of the <? extends Number> wildcard: the method parameter signature setFirst(? extends Number) cannot accept any subclass of Number.

The only exception to this rule is that we can pass null as a method argument:

java
p.setFirst(null); // ok, but will throw NullPointerException later
p.getFirst().intValue(); // NullPointerException

The Purpose of the extends Wildcard

If we examine the Java standard library's java.util.List<T> interface, it implements a list similar to a "dynamic array," with main functionalities including:

java
public interface List<T> {
    int size(); // Get the number of elements
    T get(int index); // Get the specified element by index
    void add(T t); // Add a new element
    void remove(T t); // Remove an existing element
}

Now, let's define a method to process each element in the list:

java
int sumOfList(List<? extends Integer> list) {
    int sum = 0;
    for (int i = 0; i < list.size(); i++) {
        Integer n = list.get(i);
        sum = sum + n;
    }
    return sum;
}

Why do we define the method parameter type as List<? extends Integer> instead of List<Integer>? From the perspective of the method's internal code, passing List<? extends Integer> or List<Integer> is completely equivalent. However, note the restrictions of List<? extends Integer>:

  • It allows calling the get() method to retrieve references of Integer.
  • It does not allow calling the set(? extends Integer) method with any reference of Integer (null excluded).

Therefore, the method parameter type List<? extends Integer> indicates that the method will only read elements from the list and will not modify them (because it cannot call add(? extends Integer) or remove(? extends Integer)). In other words, this is a read-only method for the parameter List<? extends Integer> (aside from maliciously calling set(null)).

Using extends to Restrict the Type of T

When defining a generic type Pair<T>, we can also use the extends wildcard to restrict the type of T:

java
public class Pair<T extends Number> { ... }

Now, we can only define:

java
Pair<Number> p1 = null;
Pair<Integer> p2 = new Pair<>(1, 2);
Pair<Double> p3 = null;

Because Number, Integer, and Double all satisfy <T extends Number>.

Types that are not Number will fail to compile:

java
Pair<String> p1 = null; // compile error!
Pair<Object> p2 = null; // compile error!

Because String and Object do not satisfy <T extends Number> as they are neither Number types nor subclasses of Number.

Summary

Using a wildcard like <? extends Number> as a method parameter indicates:

  • The method can call methods that retrieve references of Number, for example: Number n = obj.getFirst();
  • The method cannot call methods that accept references of Number (null excluded), for example: obj.setFirst(Number n);

In short, using the extends wildcard means it can read but cannot write.

Using a definition like <T extends Number> for a generic class means:

  • The generic type is restricted to Number and its subclasses.
Extends Wildcard has loaded