Autoboxing and unboxing
In the previous lesson we put the coin into the envelope by hand, with Integer.valueOf(42), and took it out with the method intValue(). Writing that at every step is tiresome. So since Java 5 the compiler does this work: it puts the coin into the envelope and takes it out by itself. The program below has not a single valueOf and not a single intValue:
public class Main {
static int twice(Integer x) {
return x * 2;
}
public static void main(String[] args) {
Integer boxed = 5;
int back = boxed;
System.out.println(back + 1); // 6
System.out.println(twice(back)); // 10
}
}Let's go line by line:
Integer boxed = 5;: on the right is anintnumber, on the left anIntegervariable. The compiler writes the call for you:Integer boxed = Integer.valueOf(5);.int back = boxed;is the opposite case: the compiler turns the line intoint back = boxed.intValue();.back + 1is an ordinary addition of twoints, and it prints6.twice(back)passes anintto a parameter of typeInteger, and the compiler boxes the argument.- Inside
twicethe expressionx * 2unboxesx: only numbers can be multiplied. The method returns10.
Boxing and unboxing
Autoboxing is the automatic conversion of a primitive into a wrapper object: the compiler inserts a call to valueOf. Unboxing is the way back: the compiler inserts intValue(), and for other wrappers longValue(), doubleValue() and so on.
It happens wherever a primitive meets a wrapper:
- in an assignment:
Integer boxed = 5;andint back = boxed;; - in a method call: an
intgoes into anIntegerparameter, or anIntegerinto anintparameter; - in arithmetic and in the comparisons
<and>:boxed + 1andboxed < 10unbox the wrapper first.
Here the envelope picture is off in one important way. A person can see that they hold an envelope and not a coin. In code you cannot see the boxing: the line int back = boxed; looks like copying a number, though a method call stands behind it.
Unboxing null
That hidden method call is the trap. Only an existing object can be unboxed, and a wrapper variable can hold null:
Integer count = null;
int total = count + 1; // ✗Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "<local1>" is nullThe program compiles and then stops at run time. There is not a single dot in the line, but the compiler put the call count.intValue() into it. There is no envelope, so there is nowhere to take the coin from. The message says so directly:
Cannot invoke "java.lang.Integer.intValue()": the hidden methodintValue()could not be called;because "<local1>" is null: because the first local variable,count, holdsnull.
As in the classes theme, java Main.java does not keep the names of local variables, so the JVM refers to count by its number. Unboxing null never gives 0: it is always a NullPointerException. Interviewers love this trap: where does a NullPointerException come from in a line without a single dot?
Boxing in a loop
Autoboxing has a cost too: every new Long object takes up room on the heap. Look at this sum:
Long total = 0L;
for (long i = 1; i <= 1_000_000; i++) {
total += i;
}
System.out.println(total); // 500000500000To the compiler, the line total += i reads like this: total = Long.valueOf(total.longValue() + i);. On every lap it unboxes the sum, adds and boxes the result into a new object. A million laps make about a million new objects that are no longer needed right away.
With long total = 0; the same loop creates no objects at all and runs noticeably faster. The rule is: compute with primitives, and use wrappers where you need an object or null.
Common mistake
A visit counter is declared as a field of type Integer:
public class Main {
static Integer visits;
public static void main(String[] args) {
visits++; // ✗
System.out.println(visits);
}
}Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "Main.visits" is nullRemember the default values of static fields: 0 for int, but null for any reference type. Integer is a reference type, so visits holds null, and visits++ tries to unbox it. Here the message names the field: Main.visits. A counter needs a primitive: static int visits; starts at 0.
Key points
- Autoboxing means the compiler calls
Integer.valueOffor you, and unboxing means it callsintValue(); this happens in assignments, method calls and arithmetic. - Unboxing
nullthrows aNullPointerException, even in a line without a single dot. - A field of type
Integerdefaults tonull, not0. - Boxing in a loop creates a new object on almost every lap, so computations use primitives.