Wednesday, August 22, 2007

Are Java Generics Worth It?

Java 5 introduced a feature called Generics that was an attempt to duplicate, more or less, the Template feature of C++. The motivation behind it was to allow the compiler to provide more type safety--making sure you don't accidentally use the wrong type object. It was also touted as a way to prevent casting. So, for example, let's say I wanted to sum the contents of a Collection of Integers, I'd write this (before Java 5):

int sum = 0; for (Iterator it = coll.iterator(); it.hasNext(); ) sum += ((Integer)it.next()).intValue();
(Note that before Java 5, I could also add a String to my collection, which would have caused a runtime error in my summation code.) In Java 5, I write this:
int sum = 0; for (Integer i : coll) sum += i;
(I'm also taking advantage of another new Java 5 feature called autoboxing, but I digress...) If I tried adding a String to my collection, the compiler wouldn't let me, thus preventing the runtime error.
This is all wonderful, you may think, but there are downsides I've discovered:

  • Extra Keystrokes: I may avoid casting and its associated keystrokes with Generics, but I have to type a lot more just to declare that I'm using Generics. Compare the two Collection instantiations below.
    Collection coll = new ArrayList(); // pre-Java 5 Collection<Integer> coll = new ArrayList<Integer>(); // Java 5
    Hmm...Java 5 is a little bit wordier. But it can easily get worse, like if I wanted, say, a Map of Strings to a Collection of Integers:
    Map map = new HashMap(); // pre-Java 5 Map<String,Collection<Integer>> map = new HashMap<String,Collection<Integer>>(); // Java 5
    Java 5 doesn't require you to explicitly specify Generic types, but it will give you a warning if you're not.
  • Type "Interference": For lack of a better way of describing this, sometimes in an attempt to use Genericized code, you can find yourself struggling to placate the compiler so that it lets you do what you want to do. I think the issue is partially related to backwards compatability, wildcards, and covariance-- which is a fancy way of saying that subtypes and supertypes aren't interchangable. This article does a good job of explaining the quirks. It'll suffice to say that nuances and subtleties with Generics sometimes hinders your ability to program more than the type safety provided helps you program.
  • Compiler Errors: I stick with the Sun compilers, so it could be their problem, but I depending on the version of the compiler I use, sometimes my complex Generics code compiles, sometimes it doesn't. We used to have problems with JDK 1.5.0_08 but had those problems resovled by _09. Now we're getting a new compiler errors after switching to 1.6.0_02. You really need to execise more caution with JDK upgrades when your code relies on Generics.
  • Unreadable Code: This is the most troubling of the problems I have with Generics. A mark of a good programmer, in my opinion, is working code that is readable and therefore understandable. But sometimes if there's some code that is Genericized using other Genericized code, you get this snowball effect and before you know it your code is littered with so many angle brackets you'd think you were writing XML. Check out this class declaration from some code written for my latest work project:
    public abstract class FooDAG<F extends Foo,     B extends Bar<F, F, B>,     E extends FooDAG.Edge<F, B, E>>   extends FooGraph<F, B, E, FooDAG.Node<F, B, E>>   implements Iterable<FooDAG.Node<F, B, E>>
    Even though I helped write this class, I honestly don't fully understand at this time what it does or why it needs to be that way, at least without a good 15 minutes reading the class over.

In any case, I'm beginning to conclude that the extra type safety offered by Generics isn't worth it, especially nowadays with with popularity of loosely-typed dynamic languages and dependency injection frameworks like Spring, which let you retrieve objects from a Factory which you need to explicitly cast into the object of your choice. I do enjoy using the Generics built in to the JavaSE API, namely the java.util package, but as far as using Generics in your own private APIs, I don't think it's worth it.

No comments: