Skip to content
Lesson 5 of 7

Step 1 of 7 · Reading · ~3 min

Learn

Generics, Streams, and Functional Java

Generics In Depth

In Java Intermediate you wrote generic classes, bounded a type parameter with extends and met ? extends / ? super through PECS. This lesson covers what those rules cost and where they stop working: erasure, the Comparable<? super T> bound, wildcard capture and the type token. You will be able to write a signature that accepts the widest legitimate set of arguments.

Erasure, and what it forbids

The compiler checks generic types and then erases them: List<String> and List<Integer> are both plain List at run time. Consequences:

void show(List<String> a) { }
void show(List<Integer> b) { }          // error: both erase to show(List)

if (obj instanceof List<String>) { }    // error: only List<?> is checkable
T[] arr = new T[10];                    // error: the array needs the real type
T item = new T();                       // error: there is no T at run time

Casts inside generic code are only checked at the call site, which gives you a delayed failure:

List<String> names = new ArrayList<>();
List raw = names;                       // unchecked warning, still compiles
raw.add(42);                            // the list now holds an Integer
String s = names.get(0);                // ClassCastException here, far from the bad add

This is called heap pollution. When you need a checked conversion, pass the class as a type token:

static <T> T read(Map<String, Object> cfg, String key, Class<T> type) {
    return type.cast(cfg.get(key));     // fails immediately, naming the wrong type
}
int port = read(cfg, "port", Integer.class);

Comparable<? super T>

Suppose Student extends Person and Person implements Comparable<Person>. A Student can be compared to any Person, but it is not a Comparable<Student>. So this bound rejects List<Student>:

static <T extends Comparable<T>> void sortDescending(List<T> xs)            // sortDescending(students) does not compile
static <T extends Comparable<? super T>> void sortDescending(List<T> xs)    // accepts Person and Student lists

? super T reads "comparable to T, or to anything T inherits from". This is why Collections.sort and Collections.max have that odd-looking bound. Whenever the type parameter is the argument of Comparable, Comparator or Consumer, you want super.

Wildcard capture

A List<?> holds elements of one unknown type, so list.set(0, list.get(1)) is rejected: the compiler cannot show the two ? are the same type. A generic helper gives the unknown type a name for the length of one call:

static void reverse(List<?> list) { reverseOf(list); }          // the wildcard is captured as E

private static <E> void reverseOf(List<E> items) {
    for (int i = 0, j = items.size() - 1; i < j; i++, j--) {
        E tmp = items.get(i);
        items.set(i, items.get(j));
        items.set(j, tmp);
    }
}

Choosing between a type parameter and a wildcard

Use a wildcard when the type appears once and you do not need to name it (List<? extends Number> to read numbers). Use a named T when the same type links two places: the return type to a parameter, or two parameters to each other (<T> void fill(List<? super T> dst, T value)). A parameter that both reads and writes cannot be a wildcard.

Varargs

static <T> List<T> listOf(T... xs) creates a T[] at the call site, which is a polluted array. Mark such a method @SafeVarargs only if it neither stores into nor leaks that array.

Common mistakes

  • <T extends Comparable<T>> for an element type that inherits its ordering. It fails to compile for the subclass; use Comparable<? super T>.
  • Adding to a List<? extends X>. Only null can be added, because the real element type is unknown.
  • Treating List<Dog> as a List<Animal>. Generics are invariant; use List<? extends Animal> to read.
  • Raw types in new code. The bad add succeeds and the exception arrives somewhere unrelated.

Your exercise

Two generic utilities, maxOf and moveAll, are stubbed in the starter and used on a List<Dog> where Dog only inherits its ordering from Animal. The likely mistake is a bound that compiles for Animal but not for Dog.

Up nextStreams APIGenerics, Streams, and Functional Java

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…