Occasionally, you need to convert a primitive type like int to an object. All primitive types have class counterparts. For example, a class Integer corresponds to the primitive type int. These kinds of classes are usually called wrappers. The wrapper classes have obvious names: Integer, Long, Float, Double, Short, Byte, Character, and Boolean. (The first six inherit from the common superclass Number.) The wrapper classes are immutable—you cannot change a wrapped value after the wrapper has been constructed. They are also final, so you cannot subclass them.
Suppose we want an array list of integers. Unfortunately, the type parameter inside the angle brackets cannot be a primitive type. It is not possible to form an ArrayList<int>. Here, the Integer wrapper class comes in. It is OK to declare an array list of Integer objects.
var list = new ArrayList<Integer>();
is automatically translated to
list.add(Integer.valueOf(3));
This conversion is called autoboxing.
Conversely, when you assign an Integer object to an int value, it is automatically unboxed. That is, the compiler translates
int n = list.get(i);
into
int n = list.get(i).intValue();
Automatic boxing and unboxing even works with arithmetic expressions. For example, you can apply the increment operator to a wrapper reference:
Integer n = 3;
n++;
The compiler automatically inserts instructions to unbox the object, increment the resulting value, and box it back.
In most cases, you get the illusion that the primitive types and their wrappers are one and the same. There is just one point in which they differ considerably: identity. As you know, the == operator, applied to wrapper objects, only tests whether the objects have identical memory locations. The following comparison would therefore probably fail:
Integer a = 1000;
Integer b = 1000;
if (a == b) . . .
However, a Java implementation may, if it chooses, wrap commonly occurring values into identical objects, and thus the comparison might succeed. This ambiguity is not what you want. The remedy is to call the equals method when comparing wrapper objects.
There are a couple of other subtleties about autoboxing. First off, since wrapper class references can be null, it is possible for autounboxing to throw a NullPointerException:
Integer n = null;
System.out.println(2 * n); // throws NullPointerException
Also, if you mix Integer and Double types in a conditional expression, then the Integer value is unboxed, promoted to double, and boxed into a Double:
Integer n = 1;
Double x = 2.0;
System.out.println(true ? n : x); // prints 1.0
Finally, let us emphasize that boxing and unboxing is a courtesy of the compiler, not the virtual machine. The compiler inserts the necessary calls when it generates the bytecodes of a class. The virtual machine simply executes those bytecodes.
You will often see the number wrappers for another reason. The designers of Java found the wrappers a convenient place to put certain basic methods, such as those for converting strings of digits to numbers.
To convert a string to an integer, use the following statement:
int x = Integer.parseInt(s);
This has nothing to do with Integer objects—parseInt is a static method. But the Integer class was a good place to put it.
The API notes show some of the more important methods of the Integer class. The other number classes implement corresponding methods.
public static void triple(Integer x) // won’t work
{
…
}
The problem is that Integer objects are immutable: The information contained inside the wrapper can’t change. You cannot use these wrapper classes to create a method that modifies numeric parameters.
If you really want to write a method to change numeric parameters, you can use one of the holder types defined in the org.omg.CORBA package: IntHolder, BooleanHolder, and so on. Each holder type has a public (!) field value through which you can access the stored value.
public static void triple(IntHolder x)
{
x.value = 3 * x.value;
}
Source: Horstmann Cay S. (2019), Core Java. Volume I – Fundamentals, Pearson; 11th edition.