Wednesday, February 27, 2013

Beware of Autoboxing

Autoboxing is a feature of Java which allows the compiler to automatically translate between primitives and their boxed object forms, e.g. from ints to Integers and vice versa. Since Java generics can only parametrize  on real classes, autoboxing is convenient when you want to use collections like List<Integer>. Instead of having to wrap all values with the Integer class, the Java compiler will do that for you. While autoboxing is known to have performance issues in some cases (constantly creating objects as you call methods with primitives will have some overhead), there are even more subtle dangers that you should be aware of. Consider the following code:

It puts two values into a map, removes one, and then prints the content of the map. Simple enough, but here's the output:

Well that's interesting; somehow "foo" did not get removed from the map. Looking closely at the Java API for Map, we see that the remove method takes an Object as its parameter (the reasons for this are subtle and discussed in various places). So when we call remove on the map, the value 1 gets autoboxed to... an Integer. Unfortunately, the map uses Longs for keys, and the Integer 1 is not equal to the Long 1, so remove does not find the key and does nothing. But since the argument to remove has type Object, the code compiles fine and you are blissfully unaware of the impending doom until your system crashes and burns horribly (or you catch the mistake with a unit test!). Sad times. And all of this is fixed by writing "1L" (primitive long) instead of just "1" (primitive int).

This subtle interaction between the collection APIs and autoboxing should make any Java programmer very anxious. It is difficult to spot such mistakes in code, especially because you expect the compiler to catch this kind of error. The best solution I have for avoiding this problem is being very rigid about passing the correct type of primitive; if a value is an int, don't ever store it in a long, and vice versa. This can be somewhat annoying (personally, "1L" is much uglier to read than "1") but may save you some headaches in the future. Alternatively, don't use boxed primitives in collections, but that seems like an even harder standard to maintain. Regardless, add autoboxing to your list of things in Java that you just have to deal with even though the point is that you shouldn't have to think about it.

No comments:

Post a Comment