The book Java Generics and Collections (O’Reilly 2007) by Maurice Naftalin and Philip Wadler provides a grand tour of generics as available within Java. I feel I have a much better handle on the topic now. I only read the first part of the book (the rest is about Collection classes). What the book lacks is a comparative perspective across Java, Scala etc. And also any type-theoretic analysis of Java generics. The book stays firmly within Java specific semantics and its quirks.
First, here’s a code sample I made up that exercises the tricky bits about generics and subclasses. If you’re not able to make sense of this code, this book might be worth your time. Or you can read the great Java Generics tutorial by Gilad Bracha or this StackOverflow answer-plus-thread by Jon Skeet where this issue is thrashed out better than I can ever explain!
class Value<E> {
E value;
Value(E value) { this.value = value; }
E get () { return value; }
void accept(E other) { System.out.println(other); }
public static void main(String[] args) {
Value<Number> numVal = new Value(1);
Value<Integer> intVal = new Value(2);
Value<Integer> intVal2 = new Value(3);
// compile error
// numVal = intVal;
// compile error
// process1(numVal, intVal);
process1(intVal, intVal2);
process2(numVal, intVal);
process3(numVal, intVal);
process4(numVal, intVal);
Value<? extends Number> numExtVal = intVal;
// compile error
// process1(numVal, numExtVal);
process2(numVal, numExtVal);
process3(numVal, numExtVal);
process4(numVal, numExtVal);
}
public static <T> void process1(Value<T> accept, Value<T> give) {
accept.accept(give.get());
}
public static <T> void process2(Value<T> accept, Value<? extends T> give) {
accept.accept(give.get());
}
public static <T> void process3(Value<? super T> accept, Value<T> give) {
accept.accept(give.get());
}
public static <T> void process4(Value<? super T> accept, Value<? extends T> give) {
accept.accept(give.get());
}
}
Chapter 5 (Evolution, not Revolution) explains that generics in Java were implemented without losing binary compatibility with older code. Hence it’s largely a feature of the compiler and the JVM is unaware of it. Also, unlike C++, different specializations for the same generic type do not result in multiple versions of the code being generated. But the downside of these decisions is that some behaviour that works with non-generic types is not possible with generic types. Chapter 6 (Reification) gets into this and related rough edges. I highlight these two chapters because without them you may not understand how the whole system hangs together. These two chapters demonstrate the authors’ mastery of the subject, and contain material that you may not find easily elsewhere.
But even without going into all those hairy bits, the first few chapters (Chapter 2: Subtyping and Wildcards; Chapter 3: Comparison and Bounds) should give a great pay off. A few hours spent here should address blind spots in one’s knowledge and also reduce trial-and-error approaches when programming with generics. Due to them I now have a much better appreciation of the Comparable/Comparator interfaces and also some decisions made in the way they’ve implemented the Enum classes. Fascinating stuff.
Chapter 8 (Effective Generics) and Chapter 9 (Design Patterns) serve like a refresher of the concepts explained till then. The Visitor and Observer patterns implemented using generics are quite interesting, and show how you might gain leverage from generics in real world use cases.
Overall, this book is definitely worth a read/skim to expand your horizons regarding generics. And ought to be an essential addition to one’s reference set of books if you’re programming in Java over the long term.