Wednesday, April 30, 2008

Introduction to Autoboxing

This Tech Tip reprinted with permission by java.sun.com

Although the Java programming language is an object-oriented language, it's often the case that when using the language you need to work with primitive types. Before J2SE 5.0, working with primitive types required the repetitive work of converting between the primitive types and the wrapper classes. In this tip, you will see how the new autoboxing feature in J2SE 5.0 handles conversions -- for example, between values of type int and values of type Integer. The tip also discusses some autoboxing-related considerations in determining when two numerical values are equal.

The October 5, 2004 Tech Tip, Formatting Output with the New Formatter discussed a new way to format output that is similar to that of the C language's printf. An example in that tip used the printf() method to print an integral value. Here is a simple example that uses the printf() method:

public class FormatPrint {
public static void main(String[] args) {
System.out.printf("There is only %d thing.", 1);
}
}

The signature of the printf() method in the FormatPrint example is:

   printf(String format, Object... args)

The number 1 is a primitive and not an object, so you might think that the line:

   System.out.printf("There is only %d thing.", 1);

should not compile. However autoboxing takes care of the situation by automatically wrapping the integer value in the appropriate wrapper object. In J2SE 1.4 you would have needed to manually wrap the primitive value using something like new Integer(1).

Another example of where automatically converting from a primitive might be useful is when you use the Collections APIs. The collections classes are designed to store objects. Consider the following simple example of storing int values from 0 to 9 in an ArrayList:

import java.util.ArrayList;

public class Autoboxing {
public static void main(String[] args) {
ArrayList list = new ArrayList();
for(int i = 0; i < 10; i++){
list.add(i);
}
}
}

The comparable program for J2SE 1.4.2 would be the following:

import java.util.ArrayList;

public class ManualBoxing {
public static void main(String[] args) {
ArrayList list = new ArrayList();
for(int i = 0; i < 10; i++){
list.add(new Integer(i));
}
}
}

With ManualBoxing you need to explicitly create the Integer object using list.add(new Integer(i)). Contrast that with Autoboxing, where the int i is autoboxed to an Integer object in the line list.add(i).

Autoboxing works well with other new J2SE 5.0 features. For example, the autoboxing feature allows seamless integration between generic types and primitive types. In the ManualBoxing example, the elements of the ArrayList are of type Object. By comparison, in the Autoboxing example, the elements of list are of type Integer.

Let's extend the Autoboxing example to iterate through the elements in the ArrayList and calculate their sum. Notice that this new version also uses the new J2SE 5.0 enhanced for loop to iterate through the elements.

import java.util.ArrayList;

public class Autoboxing {
public static void main(String[] args) {
ArrayList list = new ArrayList();
for(int i = 0; i < 10; i++){
list.add(i);
}
int sum = 0;
for ( Integer j : list){
sum += j;
}
System.out.printf("The sum is %d.", sum );
}
}

Autoboxing is used in a number of places in the updated Autoboxing example. First, ints are boxed to Integers as they are added to the ArrayList. Then Integers are unboxed to ints to be used in calculating the sum. Finally, the int representing the sum is boxed for use in the printf() statement.

The transparency of the boxing and unboxing makes autoboxing easy to use. However using the autoboxing feature requires some care. In particular, testing for the equality of objects created by autoboxing is not the same as testing for the equality of objects that are not created by autoboxing. To see this, look at the following BoxingEquality class:

import java.util.ArrayList;

public class BoxingEquality {
public static void main(String[] args) {
int i = 2;
int j = 2;
ArrayList list = new ArrayList();
list.add(i);
list.add(j);
System.out.printf("It is %b that i ==j.\n",
(i==j)); //(1)
System.out.printf("It is %b that " +
"list.get(0) == list.get(1).\n",
list.get(0)==list.get(1)); //(2)
System.out.printf("It is %b that " +
"list.get(0).equals(list.get(1)).",
list.get(0).equals(list.get(1))); //(3)
}
}

The first print statement in BoxingEquality compares the equality of the primitives i and j. The second print statement compares the equality of the objects created by autoboxing i and j. The third print statement compares the value of the objects created by autoboxing i and j. You would expect the first and the third print statements to return true, but what about the second? The output from running the BoxingEquality program is:

   It is true that i ==j.
It is true that list.get(0) == list.get(1).
It is true that list.get(0).equals(list.get(1)).

Now change the values of i and j to 2000.

import java.util.ArrayList;

public class BoxingEquality {
public static void main(String[] args) {
int i = 2000;
int j = 2000;
// . . .
}

Save, recompile, and rerun BoxingEquality. This time the results are different:

   It is true that i ==j.
It is false that list.get(0) == list.get(1).
It is true that list.get(0).equals(list.get(1)).

The primitives are equal and the values of the boxed ints are equal. But this time the ints point to different objects. What you have discovered is that for small integral values, the objects are cached in a pool much like Strings. When i and j are 2, a single object is referenced from two different locations. When i and j are 2000, two separate objects are referenced. Autoboxing is guaranteed to return the same object for integral values in the range [-128, 127], but an implementation may, at its discretion, cache values outside of that range. It would be bad style to rely on this caching in your code.

In fact, testing for object equality using == is, of course, not what you normally intend to do. This cautionary example is included in this tip because it is easy to lose track of whether you are dealing with objects or primitives when the compiler makes it so easy for you to move back and forth between them.

For more information on autoboxing, see Autoboxing.

Copyright (c) 2004-2005 Sun Microsystems, Inc.
All Rights Reserved.



For details: Java Tips

No comments: