Tuesday, January 12, 2010

Boxing Day

Any guesses as to what this program prints when you run it? l = 0? l = 100? Something else?

public class Foo {
static long e(long v) { return 0L; }

static <T> T e(T v) { return null; }

static void f(long l) {
System.out.println("l = " + l);
}

public static void main(String[] args) {
Long l = 100L;
f(e(l));
}
}

As it turns out, this program fails with a NullPointerException. There are two version of the e() method, one that takes a primitive long and one that takes a generic Object reference. Since l is a Long instead of a primitive long, the compiler is able to resolve the overload without needing to do unbox l, so it resolves it to the e() method that takes a generic Object reference. The result of this method is then auto-unboxed when it is passed into f(), and since the Object reference version of e() returns null, it fails with a NullPointerException. The solution is pretty easy, just call l.longValue() to convert the value into a primitive long, or better yet, declare l as a primitive long instead of a Long in the first place.

While this example of auto-unboxing and overloading confusion may look contrived and frivolous I've run into this error a couple of times in real life using the EasyMock#eq() methods to set up mock object test case expectations.

Autoboxing and unboxing is a fairly controversial language feature in some circles. I think the benefits outweigh the drawbacks, but, as I've illustrated above, there are definitely some drawbacks. I like the Scala approach better, but there's no way to completely get rid of primitive types at the language level in Java, so autoboxing and unboxing is as good (or as bad, depending on your opinion) as it's going to get.

No comments: