Autoboxing, a feature which seems like it had squeezed into JSR 201, is a convenience feature which has its own bag of tricks. This feature seems to come as a complement to the major EoD feature in Tiger, Generics. Since generics can’t be used with primitives as their types (i.e. Can’t declare Collection<int>), their wrappers are used instead (Collection<Integer>). This is due to erasure, as most annoying things about generics are (such as reflection data, an old rant of mine).
It’s obvious to see how autoboxing provides ease of use: Just taking foo(Integer num) as an example, the difference between foo(5) and foo(new Integer(5)) is notable! To take another case as an example, a call to bar(Integer[] arr) is simplified, using the varags feature, from bar(new Integer[] { new Integer(1), new Integer(2), new Integer(3) }) to bar(1, 2, 3), so the ease of development provided by this feature is nothing to ignore.
There are things that are less obvious about this new feature, though:
- It does not involve in changing the virtual machine or the Java language
- It offers performance improvements over previous conversion and wrapping methods
- It contains its own bag of pitfalls to watch out from
Plain and common Java
Even though the stricter developers might raise an eye-brow at the somewhat odd syntax boxing and unboxing offers (such as Integer i = 5, definitely not an object being referenced there!), allow me to reassure you: Nothing weird has happened to the language.
Autoboxing works because of compiler enhancements and wrapper classes’ enhancements. The wrapper classes had been enhanced with valueOf methods, overloaded to accept its wrapped primitive and an overload for String. When a boxing syntax is encoutered, the compiler generates code that would call the appropriate wrapper with its valueOf method. In the previous example, Integer.valueOf(int) would have been called, generating the code Integer i = Integer.valueOf(5).
Unboxing is even simpler, as the compiler generates code that would call the already-existing, appropriate primitiveValue() method. For example, considering iWrapper is of type Integer, the code int i = iWrapper would be generated as int i = iWrapper.intValue().
Wrapper Pools
One might wonder why boxing doesn’t use the already-existing constructors the wrappers have, and instead uses a set of methods that are not backward-compatible. The reason, as far as I can tell, is efficiency.
The group designing the autoboxing feature had probably assumed (correctly) that once such transparency is achieved between the primitive and its wrapping class, it might be used more often and widely. Since creating a new wrapper for every boxing occurance is quite expensive, especially considering it usually being used at a single scope of a method, they designed a pool of common wrappers.
This is in fact an implementation of the flyweight design pattern. When a boxing occurs for a well-known value, instead of creating a new wrapper instance, a pre-created instance is fetched from a pool and returned. Since all wrapper instances are immutable objects, this can be done without fear of code changing the internal state of the wrapper for all of its users.
That said, it’s still not recommended to use autoboxing for scientific calculations. For example, the code d = a * b + c is using Integer classes for a, b, c and d, and the generated code is d.valueOf(a.intValue() * b.intValue() + c.intValue()). All these method invocations have their own overhead, so it’s usually recommended to use autoboxing when needed to store primitives in collections.
Everything has its pitfalls
Any feature, especially one that generates transparency code, should be used with caution in mind. Autoboxing is no exception to the rule, and has some common mistakes associated with it.
- Unboxing a null value. Since the compiler generates a call to
primitiveValue, it is important to understand that if the value to be unboxed (in the collection, for example) is null, aNullPointerExceptionwill be thrown! It is usually a good practice to check the value of wrapper before unboxing, such as:
Integer wrapped = map.get(location);
if (wrapped == null) { ... } // handle this situation.int i = wrapped;
equalsand==are tricky with boxed values. Since wrappers are now cached in a pool of instances, it is very tempting to try the following code:
Long w1 = 1000L;
Long w2 = 1000L;
if (w1 == w2) {
// do something
}
Since it might actually work (especially true for smaller values). However, the Java language specifications states:
Ideally, boxing a given primitive value p, would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques.
In other words, this won’t always work. Remember that the
==operator compares refernece values. If the boxed value of 1000 is not pooled, thenw1andw2will be instanciated as two different instances, thus different in their reference values. Note, however, that using the==operator against primitive values is fine: The compiler prefers to unbox when possible, so the raw value will be compared.- Not using the instances pool. While this is more of a bad practice than a potential bug, it is worth mentioning. As said above, the wrappers are now implementing a flyweight design pattern. That said, using wrapper’s constructors might impact an application’s performance, as it will create and release instances all the time, when it might have been using pre-created, uncollected instances instead.
February 27th, 2006 at 5:45 am
I like your set of articles. Very informative for someone intending to learn 1.5. I have already bookmarked your page.
February 27th, 2006 at 7:17 am
Thanks Kris! I’m glad you like them!
March 2nd, 2006 at 5:40 pm
hi dude. I too like your articles. Kudos for the wonderful job you are doing. Getting loads of new info from your blog. Keep going good work.
March 2nd, 2006 at 10:59 pm
Max, thanks a lot! Comments like yours and Kris’ really warm my heart.
Thanks again!
April 6th, 2008 at 6:43 am
[...] of a performance problem but the fact is that in the simple cases (which are the majority, I hope) the flyweight implementation of valueOf, together with automatic inlining of methods by the JVM, will remove any overheads created by this [...]