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; useComparable<? super T>.- Adding to a
List<? extends X>. Onlynullcan be added, because the real element type is unknown. - Treating
List<Dog>as aList<Animal>. Generics are invariant; useList<? 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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…