Unboxing Java Boxing

Posted on Posted in Java

“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.

  1. 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 is x1.equals(x2).

  2. 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 that valid is a Boolean, not a boolean, and like all object reference fields, its initial value is null. This may go unnoticed for some time as valid may obtain its value from a setter or constructor. When the program evaluates (valid) — which is the same as (valid == true), it is comparing a Boolean to a boolean. When you mix primitives and boxed primitives in a single operation, the boxed primitive is auto-unboxed. If a null object reference is auto-unboxed, you get a NullPointerException.

  3. 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) as Long instead of the primitive long. The variable is repeatedly boxed and unboxed causing the performance degradation.

  4. 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 and false. You may be wondering and may think that this must print either true and true or for the very least false and false. 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);
  5. 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 to set.remove(i) selects the overloading remove(E) — where E is an Integer, and autoboxes i from int to Integer. So the program ends up removing the positive values from the set. On the other hand, the call to list.remove(i) selects the overloading remove(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, cast list.remove‘s argument to Integer, forcing the correct overloading to be selected.

    Prior to release 1.5, the List interface had a remove(Object) method in place of remove(E), and the corresponding parameter types, Object and int, 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 the List 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

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.