An Unfulfilled Promise?

Over nine years ago, Generics, or put another way, the angle bracket notation <>, was introduced in Java 1.5 to great fanfare. The promise of generics was at a high level two-fold: (1) To introduce strong compiler enforced type casting to the language and hence reduce run-time errors. (2) To allow for the "generic" declarations of methods and classes. This would in theory allow for more reusable code without resorting to the Object base class in all instances where a generic declaration was desired.

In fact, many common classes in the Java language such as the entire Collections Framework which includes the List interface were modified to allow the use of generics. It was no doubt the vision of the language designers that generics would be a ubiquitous and welcome way of coding for Java developers. However, the implementation of generics in the Java language is still a source of controversy. It's also a good bet that many developers may not relish the sight of <> in a code base with which they are not intimately familiar. We will expand on this second point later.

So, what is the nature of the controversy surrounding generics? Does the use of generics assist or hinder you in writing safe, maintainable code? Are there guidelines surrounding the use of generics in your codebase?

Generics: The Controversy

Let's first introduce the use of generics with our first code example:

Pre Java 1.5

List peanuts = new ArrayList ();
peanuts.add (new Peanut());
peanuts.add (new Cashew ()); // no compilation error
Peanut peanut = (Peanut) peanuts.get (1); // runtime ClassCast Exception

This code will compile, but a runtime exception will be raised because the JVM assumed you knew that were dealing with peanuts not cashews. Duh!

Post Java 1.5 using Generics

List peanuts = new ArrayList<>(); // As of Java 1.7 no needed on right side
peanuts.add (new Peanut());
peanuts.add (new Cashew ()); // Compile time error!!

The above code will not compile. This is a good thing! The programmer can now rectify the problem while coding rather than finding out at run-time, which would of course stay hidden until it was running in production.

Nothing too controversial so far. However, the controversy begins to arise when generics, pardon the pun, becomes, well, generic. So line 1 in the above code can also be written as:

List peanuts = new ArrayList();

Where T is an object of any type to be defined later. This can lead down a dark path towards indecipherable declarations such as :

public class T2 {
T3 object 1;
T4 object2;
public static void fill (List list, T6 val) { … }
} //Where T through T6 are type placeholders to be defined later.

That is, the use of generics can be propagated across class, interface and method declarations seemingly ad infinitum with concrete class and type definitions obscured by layers of generic abstractions. Critics point out that run-time errors in the above code are hard to track down. Run-time errors? …But generics gets rid of run-time errors? Not exactly. Once the generic abstraction reaches class and interface definitions, run-time errors occur because generics cannot be strictly enforced due to backward compatibility requirements. This leads into another discussion on type erasure, a subject for another day.

No doubt though, the nature of the controversy is clear. Not only do generics introduce to the nth degree the problem it is supposed to mitigate but it is a fairly complex construct to fully grasp. Very few will relish spending time deciphering generic declarations. Which raises an interesting question...Since the human brain normally recoils from generic abstractions, how would it appear in a code base? This likely occurs through the cumulative effect of programmers reluctantly perpetuating an earlier established generic pattern. Eventually and inadvertently, the code base may contain convoluted declarations like the one above.

Generics: Avoid or Embrace?

So is the answer to avoid generics altogether? No. In this case we do not need to throw out the baby with the bathwater. For example, as we first illustrated, the use of generics while manipulating collections such as lists provide useful compile time checks that make for safer more reliable code. Even expanding the use of generics to method and class declarations does not necessarily spell doom. For instance, a robust implementation of the Factory design pattern can make good use of generics to provide adaptable, extensible code.

Generics: Be Kind to Those That Come After You

The advice when implementing generics is to first employ it in its simplest form where it provides compile time casting checks. In fact, it is worth every developer's time to be comfortable enough with generics to apply it in this context.

When it comes to applying generics in its, dare we repeat the pun, more generic form, the basic design principle of "keep it simple… " applies. Ask questions such as: Does the extensibility or adaptability of using generics in this instance justify the added complexity introduced in the code base? Do I need to perpetuate a generic definition or is it time for the rubber to meet the road by using concrete definitions? And my favorite question: Although I love using generics, will introducing generics here unnecessarily complicate the day of the developer after me?

In closing, by all means spend the time necessary to understand generics so you can apply it in ways that improve the reliability of your code. However, once you comprehend it, be gentle in your application of the angle brackets <> , think of the humans that must maintain the code after you.