“I thought I knew boxing, but I found out I didn’t.” George St. Pierre said after working with Freddie Roach.
There are two types in Java Programming Language. The first is primitive type such as int, double,
and boolean
. The second is the reference type such as String, List,
and Set
. Every primitive type has a corresponding reference type, called a boxed primitive. The boxed primitives corresponding to int, double,
and boolean
are Integer, Double
and Boolean
.
Java release 1.5 supports automatic conversion of primitive types to their corresponding Boxed primitive types in assignments, and method and constructor invocations. This conversion is known as autoboxing. It also supports automatic unboxing, where Boxed primitive types are automatically converted into their primitive equivalents if needed for assignments, or method or constructor invocations.
These features blur but do not erase the distinction between the primitive and boxed primitive types. It is very important that you are aware of which you are using, and that you choose carefully between them. I will specify some important points that you need to consider to avoid getting into trouble in using these primitive and boxed primitive types.
- Primitives have only their values, whereas boxed primitives have identities distinct from their values. Consider the code snippet below:
Integer x1 = 150; Integer x2 = 150; int a1 = 150; int a2 = 150; System.out.println(x1 == x2); System.out.println(a1 == a2);
This prints false and then true. You may think that this will print true and true. The
x1 == x2
uses the Java Identity check, that is if they refer to the same instance on the heap. The second is the value check. One way to fix this is to use the equals method, that isx1.equals(x2)
. - Primitive Boxed types can contain a null value whereas the primitive types cannot. Consider the code snippet below:
private Boolean valid; public void foo( ) { if (valid) { System.out.println("VALID"); } else { System.out.println("INVALID"); } }
This code may potentially throw a
NullPointerException
when evaluating(valid)
. The problem is thatvalid
is aBoolean
, not aboolean
, and like all object reference fields, its initial value isnull
. This may go unnoticed for some time asvalid
may obtain its value from a setter or constructor. When the program evaluates(valid)
— which is the same as(valid == true)
, it is comparing aBoolean
to aboolean
. When you mix primitives and boxed primitives in a single operation, the boxed primitive is auto-unboxed. If anull
object reference is auto-unboxed, you get aNullPointerException
. - Primitives are more time- and space-efficient than boxed primitives. The code below will compile without error nor warning.
Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum);
However, this is much slower than it should be because it accidentally declares the local variable (
sum
) asLong
instead of the primitivelong
. The variable is repeatedly boxed and unboxed causing the performance degradation. - Be aware of the Byte, Short, Integer and Long Constant Pool. Consider the following code snippet:
Integer i1 = 100; Integer i2 = 100; Integer j1 = 128; Integer j2 = 128; System.out.println(i1 == i2); System.out.println(j1 == j2);
This will print
true
andfalse
. You may be wondering and may think that this must print eithertrue
andtrue
or for the very leastfalse
andfalse
. Where’s the consistency here? The JVM actually maintains pool of values for these data types ranges from -128 to 127. Creating Object using these data types that falls in that range will assign objects from the pool and thus the Java Identity test works — and that’s what happens in(i1 == i2)
. If you assign value outside of that range, it will create a new different object and thus the Java Identity test fails — and that’s what happens in(j1 == j2)
.On the contrary, using Constructor to create an Object using those types will ignore the pool even if the value falls from -128 to 127. The code below will print
false
.Integer y1 = new Integer(1); //Bad Practice: use Integer.valueOf instead Integer y2 = new Integer(1); //Bad Practice: use Integer.valueOf instead System.out.println(y1 == y2);
- Be aware of method overloading. This may sounds a separate or unrelated issue but I just want to point out the effect of Boxing (if not carefully used) along with the addition of Generics. The code below adds the integers from -3 through 2 to a sorted set and to a list.
Set set = new TreeSet(); List list = new ArrayList(); for (int i = -3; i < 3; i++) { set.add(i); list.add(i); } for (int i = 0; i < 3; i++) { set.remove(i); list.remove(i); } System.out.println(set + " " + list);
Most programmer may expect the program to remove the non-negative values
(0, 1, 2)
from the set and the list, and to print[-3, -2, -1] [-3, -2, -1]
. However, the program removes the non-negative values from the set and the odd values from the list, thus printing[-3, -2, -1] [-2, 0, 2]
. This may sound confusing. Here’s what’s happening: The call toset.remove(i)
selects the overloadingremove(E)
— whereE
is anInteger
, and autoboxes i fromint
toInteger
. So the program ends up removing the positive values from theset
. On the other hand, the call tolist.remove(i)
selects the overloadingremove(int i)
, which removes the element at the specified position from the list. Thus if you remove the elements at index 0, 1 and 2, you’re left with[-2, 0, 2]
. To fix the program, castlist.remove
‘s argument toInteger
, forcing the correct overloading to be selected.Prior to release 1.5, the
List
interface had aremove(Object)
method in place ofremove(E)
, and the corresponding parameter types,Object
andint
, were “radically different”. Two types are radically different if it is clearly impossible to cast an instance of either type to the other. But in the presence of generics and autoboxing, the two parameter types are no longer radically different. In other words, adding generics and autoboxing to the language damaged theList
interface. Always keep this behavior in mind when working with method overloading and primitive and boxed primitive types. To learn more about method overload, please refer to Item 41 (Use Overloading Judiciously) in the Effective Java by Josh Bloch.
As a summary, prefer Primitive types to Boxed Primitives (Item 49). Primitive types are simpler and faster. You should only use Boxed Primitives in situation where you are not permitted to use primitve types such as: (1) as elements, keys and values in collections, (2) as type parameters in parameterized types, and (3) for reflective method invocation.
Reference:
— Effective Java Programming Language Guide (Second Edition) by Joshua Bloch
2 thoughts on “Unboxing Java Boxing”
Great!
Hi Sir…nice job…