Java: Wrapper Types

Each primitive type (int, byte, double, …) has a corresponding wrapper type (Integer, Byte, Double, …).

int i = 5;                   // primitive value
Integer j = new Integer(5);  // "boxed" value

A wrapper type “wraps” a primitive type in a class. The source code for Integer for example, looks like this:

public final class Integer extends Number implements Comparable<Integer> {
    // ...
    private final int value;
    // ...
}

There are many methods and static fields, but this is the gist of it.

The fundamental difference is that an Integer is a reference type, while an int is a primitive type. This is important so make sure you understand the difference:

Java: Primitives vs Objects and References

An object of wrapper type is often called a boxed value, and converting to and from wrapper types is called boxing and unboxing.

All Wrapper Types

There are 8 primitive types, and therefore 8 wrapper types.

Primitive Wrapper
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
char java.lang.Character
boolean java.lang.Boolean

Why boxed values?

Primitives are more efficient and have a smaller memory footprint. Why would you want to work with wrapper types?

The four primary reasons are:

1. Generics

You can’t use a primitive as type argument for a generic class or method.

Example: Generic type arguments
// error: Type argument cannot be of primitive type
List<int> list1 = new ArrayList<>();

// Works fine
List<Integer> list2 = new ArrayList<>();

(Support for primitives with generics is under way. It might become a reality in something like Java 11. See JEP 218: Generics over Primitive Types.)

2. A way to represent “undefined” (nullability)

As opposed to primitive types, a reference can be null. This means that using a wrapper type allows you to express things like “The value is undefined”, or, “The value has not yet been loaded” without having to reserve a dummy value such as −1.

3. APIs requiring reference types

Some APIs require you to work with subtypes of Object, i.e. with reference types. String.format and related methods for example.

Example:
// Compiles but doesn't do what you want
int[] arr1 = { 1, 2, 3 };
System.out.printf("%d %d %d", arr1);

// Works as intended
Integer[] arr2 = { 1, 2, 3 };
System.out.printf("%d %d %d", arr2);

4. The Number type

The numeric wrapper types extend Number:

Object Number Character Boolean Byte Short Integer Long Float Double

This allows you to, for instance, have heterogeneous collections of mixed numbers.

Example:
List<Number> numbers = new ArrayList<>();
numbers.add(new Integer(10));
numbers.add(new Double(17.9));

for (Number n : numbers) {
    System.out.printf("%.2f%n", n.doubleValue());
}

Another use case is parsing where you don’t know the appropriate numeric type in compile time. See for example NumberFormat.parse.

Autoboxing and unboxing

If a primitive value is provided where a boxed value is expected, the compiler can automatically convert it. This is called autoboxing. Same applies if a boxed value is provided where a primitive is expected. This is called unboxing.

Example: Autoboxing and unboxing
Integer i = 5;  // autoboxing
int j = i;      // unboxing

Continue reading here:

Java: Autoboxing and unboxing

Boxed values and equality

An operator such as <= is only defined for primitive types, so when applied to, say Integers, the compiler will automatically unbox the operands.

Example: Operands are unboxed.
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i <= j); // true

This is in fact the case for all operators except == and !=.

Since the == and != operators are defined for reference types, the compiler is not forced to, and will not, unbox the operands.

Example: Operands are not unboxed (and i and j refer to different objects)
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
System.out.println(i != j); // true

To complicate matters further, autoboxing may cache values, which causes reference equality to “work” for some values, but not for others. Continue reading here:

Java: Boxed values and equality

Boxed values are immutable

A boxed value can’t be changed. (As seen at the top of this article, the value field in the Integer class is final!) If you want to change an Integer variable, you’ll have to assign a new reference to it, which points at different boxed value.

Example:
Integer i = new Integer(5);

// Increment i
i = new Integer(i.intValue() + 1);

You can in fact do things like i++ and i = i + 1. This however, involves autoboxing/unboxing and in the end the result is semantically equivalent to the above.

The immutability aspect is important, as it makes boxed values safe to use as keys in hash tables.

If you’re after the advantages that wrapper types provide, but need mutability, then AtomicBoolean, AtomicInteger or AtomicLong might be viable options. Note however that these classes aren’t general purpose replacements for the corresponding wrapper classes. They don’t, for example, override equals. See separate article: AtomicInteger and equals / Comparable.

Comments