Friday, September 07, 2007

The Dangers of Auto-boxing in Java

This is one of the those subtle bugs that I have unwittingly coded in because of the inherent laziness that is afforded by Autoboxing, a feature that was available with Java 1.5 onwards.

What auto-boxing means, is that Java will perform conversion between primitives to its Object equivalents and vice-versa for you automatically, rather than via the new() constructor way that you do traditionally.

However, because of the inherent non-obvious assumptions made, it can lead to subtle bugs that can be hard to spot. It did for me, especially when it was enmeshed in a part of a long stretch of code.

To give an illustrative example of the bug:


import java.util.*;
public class Test {
static HashMap<Integer,String> hm = new HashMap<Integer, String>();

public static void main(String args[]) {
byte b = 1;
hm.put(1, "Hello World");
String s = hm.get(b);
System.out.println("The result is: " + s);
}
}


As said, this example will only compilable with 1.5 and above, so take note if you want to compile it. Java will 'intelligently' determine the type of the primitive variable and turn them into objects for you, making it easier to use primitives with utility libraries such as java.util.HashMap. But if you run it, you're going to find that it is printing 'null' rather than 'Hello World'.

Caught the bug yet?

If you haven't, let me show you what Java has implicitly converted the code into through Autoboxing:

     ...
public void main(String args[]) {
byte b = 1;
hm.put(new Integer(1), "Hello World");
String s = hm.get(new Byte(1));
System.out.println("The result is: " + s);
}
...


I've shown the offending lines in blue. As you should know, Java objects of the same value but of different types are fundamentally not equal, i.e. Integer(1) != Byte(1). That is the sole culprit of the problem, which is being masked by using Auto-boxing.

That is a good reminder to look out and be careful of pitfalls like this, one which costed me 2 hours to find. Next time I'll think twice and look triply hard before relying on Autoboxing to do the right thing again!

1 comments:

Anonymous said...

If you encapsulate access to your classes you can solve this, i.e. like:

public class SomeApp {

private static Map<Integer, String> map;

public static void main(String[] args) {
map = new HashMap<Integer, String>();
byte byteKey = 5;

put(byteKey, "Hi there!");
System.out.println(get(byteKey));
}

private static void put(int key, String value) {
map.put(key, value);
}

private static String get(int key) {
return map.get(key);
}

}

Which is probably closer to how it will be used in most cases anyway?

Post a Comment