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:
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
:
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:
Boxed values and equality
When comparing wrapper types such as Integer
s, Long
s or Boolean
s using ==
or !=
, you're comparing them as references, not as values.
If two variables point at different objects, they will not ==
each other, even if the objects represent the same value.
Example: Comparing different Integer
objects using ==
and !=
.
Integer i = new Integer(10);
Integer j = new Integer(10);
System.out.println(i == j); // false
System.out.println(i != j); // true
The solution is to compare the values using equals()
…
Example: Compare objects using .equals(…)
Integer i = new Integer(10);
Integer j = new Integer(10);
System.out.println(i.equals(j)); // true
…or to unbox the operands explicitly.
Example: Force unboxing by casting:
Integer i = new Integer(10);
Integer j = new Integer(10);
System.out.println((int) i == (int) 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.
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.