Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Bounded types and wildcards

A parameter T without bounds can be anything, so only the methods of Object are available on it. If a method needs, say, to add numbers or compare values, the type is bounded.

The extends bound

The notation <T extends Number> means "T is Number or one of its subclasses": Integer, Double, Long. Inside the method T now has the methods of the Number class, such as doubleValue():

<T extends Number> double sum(List<T> numbers) {
    double total = 0;
    for (T n : numbers) {
        total += n.doubleValue();
    }
    return total;
}
 
void main() {
    IO.println(sum(List.of(1, 2, 3)));
    IO.println(sum(List.of(1.5, 2.5)));
}

Output:

6.0
4.0

You cannot pass a list of strings to sum() — the compiler will not let it through.

Bounding by an interface

An interface can follow extends too. <T extends Comparable<T>> means "the elements can be compared with each other" — that is enough to find the largest one:

<T extends Comparable<T>> T max(List<T> items) {
    T best = items.get(0);
    for (T item : items) {
        if (item.compareTo(best) > 0) {
            best = item;
        }
    }
    return best;
}
 
void main() {
    IO.println(max(List.of(3, 9, 4)));
    IO.println(max(List.of("pear", "apple", "quince")));
}

Output:

9
quince

Several bounds are joined with &: <T extends Number & Comparable<T>>.

Lists are not inherited

Integer is a subclass of Number, but List<Integer> is not a subtype of List<Number>. Otherwise you could put a decimal into a list of integers through a variable of type List<Number>. That is why this method will not accept a list of Integer:

double total(List<Number> numbers) {
    return numbers.size();
}
 
void main() {
    List<Integer> ints = List.of(1, 2);
    IO.println(total(ints));
}

Output:

Main.java:7: error: method total in class Main cannot be applied to given types;
    IO.println(total(ints));
               ^
  required: List<Number>
  found:    List<Integer>
  reason: argument mismatch; List<Integer> cannot be converted to List<Number>

The ? extends wildcard

The solution is the wildcard ?. The type List<? extends Number> means "a list of something that is a Number". You can read numbers from such a list, but you cannot add to it:

double total(List<? extends Number> numbers) {
    double sum = 0;
    for (Number n : numbers) {
        sum += n.doubleValue();
    }
    return sum;
}
 
void main() {
    List<Integer> ints = List.of(1, 2);
    List<Double> doubles = List.of(0.5, 0.25);
    IO.println(total(ints));
    IO.println(total(doubles));
}

Output:

3.0
0.75

The ? super wildcard

List<? super Integer> is "a list you can put an Integer into": List<Integer>, List<Number> and List<Object> all fit. Such a parameter is needed by a method that writes values:

void fillWithOnes(List<? super Integer> target, int count) {
    for (int i = 0; i < count; i++) {
        target.add(1);
    }
}
 
void main() {
    List<Number> numbers = new ArrayList<>(List.of(2.5));
    fillWithOnes(numbers, 2);
    IO.println(numbers);
}

Output:

[2.5, 1, 1]

A simple rule: if a method only reads from a collection, use ? extends; if it only writes, use ? super. This rule (PECS) and type erasure are covered in detail in the PECS and type erasure lesson.

Common mistake

You cannot add to a ? extends list: the compiler does not know which list it is exactly — of integers or of decimals:

void addZero(List<? extends Number> numbers) {
    numbers.add(0);
}
 
void main() {
}

Output:

Main.java:2: error: no suitable method found for add(int)
    numbers.add(0);
           ^
    method List.add(CAP#1) is not applicable
      (argument mismatch; int cannot be converted to CAP#1)

CAP#1 is the compiler's name for the unknown type hidden behind ?. If the method must add elements, use ? super Integer or a specific type.

Thanks for reading!

Donate to the site — we work hard for you.