Why a primitive needs a wrapper
Java has two kinds of values. A primitive such as int or double holds its value directly. An object is reached through a reference and has methods. Some parts of Java work only with objects. The ArrayList<E> class in the next topic is the main example: its type parameter E must be a class, so you can write ArrayList<Integer> but not ArrayList<int>.
A wrapper class closes the gap. It is a class whose objects each hold one primitive value. The course uses two:
Integerwraps anint.Doublewraps adouble.
Both are in the java.lang package, so no import is needed. Both are immutable: once an Integer object is created, its value cannot change. Think of a sealed envelope with a number inside. You can read the number or replace the whole envelope, but you cannot rewrite the number.
Autoboxing and unboxing
You rarely create wrapper objects by hand. The compiler converts for you.
- Autoboxing is the automatic conversion from a primitive to its wrapper:
inttoInteger,doubletoDouble. - Unboxing is the automatic conversion from a wrapper back to the primitive.
The compiler applies autoboxing when a primitive is
- passed as a parameter to a method that expects the wrapper type, or
- assigned to a variable of the wrapper type.
It applies unboxing when a wrapper object is
- passed as a parameter to a method that expects the primitive, or
- assigned to a variable of the primitive type.
Arithmetic on wrapper objects also unboxes them first, since +, -, *, / and % work on primitives. Figure 1 shows the two directions.
The conversion is exact, not a cast. int boxes only to Integer, and double boxes only to Double. That has an important consequence, which the practice set tests.
Double d1 = 7.0; // fine: double boxes to Double
double d2 = 7; // fine: int widens to double (not boxing)
Double d3 = 7; // does NOT compile: an int cannot be boxed to a Double
Compiling the third line gives the compiler message incompatible types: int cannot be converted to Double.
Turning a String into a number
Text read from a file (Topic 4.6) or cut out with split is a String. Two class methods from the Java Quick Reference convert it:
| Method | Returns | Example | Result |
|---|---|---|---|
static int parseInt(String s) |
the int the String holds |
Integer.parseInt("42") |
42 |
static double parseDouble(String s) |
the double the String holds |
Double.parseDouble("4") |
4.0 |
They are class methods, called on the class name, not on an object. The String must contain exactly a number. If it does not, the method throws a NumberFormatException. Run on each of these, all three threw it: "12a", "4.0" given to parseInt (an int cannot hold a decimal point) and " 5" with a leading space. A String such as "007" is fine and gives 7.
A common source of error is joining versus adding. "42" + "8" is the String "428", but Integer.parseInt("42") + Integer.parseInt("8") is the number 50. Both were run.
Worked example 1: tracing boxing and unboxing
Task. Trace the code and give each variable’s final value and the output.
Integer a = 5;
int b = a + 3;
Double d = 2.5;
double e = d * b;
Integer c = b;
System.out.println(a + " " + b + " " + d + " " + e + " " + c);
Line by line.
| Line | What the compiler does | Value stored |
|---|---|---|
Integer a = 5; |
autoboxes the int 5 into an Integer |
a refers to an Integer holding 5 |
int b = a + 3; |
unboxes a to 5, adds 3 as ints, stores an int |
b is 8 |
Double d = 2.5; |
autoboxes the double 2.5 |
d refers to a Double holding 2.5 |
double e = d * b; |
unboxes d to 2.5; b (8) is widened to 8.0; multiplies |
e is 20.0 |
Integer c = b; |
autoboxes b |
c refers to an Integer holding 8 |
Output. 5 8 2.5 20.0 8. Running this gives exactly that line. Notice that printing a wrapper object shows its value (5, 2.5), not a memory address, because Integer and Double define how they are displayed.
Check. e has a decimal point because one operand of * was a double; b is an int that Java widened. If you had written a / 2 with a holding 5, both operands unbox to int, so the result would be 2.
Worked example 2: averaging text values
A line from a file is 3.5,4.25,2. Write meanOf(String[] vals) to return the mean of the numbers in the array, or 0.0 if the array is empty, then apply it to that line.
public static double meanOf(String[] vals)
{
if (vals.length == 0)
{
return 0.0;
}
double total = 0;
for (String s : vals)
{
total += Double.parseDouble(s);
}
return total / vals.length;
}
Trace for "3.5,4.25,2".split(","), which gives the array {"3.5", "4.25", "2"}:
s |
Double.parseDouble(s) |
total after |
|---|---|---|
| (start) | 0.0 | |
"3.5" |
3.5 | 3.5 |
"4.25" |
4.25 | 7.75 |
"2" |
2.0 | 9.75 |
The method returns 9.75 / 3 = 3.25. Running meanOf on this array printed 3.25.
Why parseDouble and not parseInt? The first token has a decimal point, and Integer.parseInt("3.5") would throw an exception. Double.parseDouble("2") is fine, as it returns 2.0.
Why total is a double and vals.length is an int. The final division is a double divided by an int, so the int widens and the division keeps its decimals. Had total been an int, the sum could not hold 3.5 at all.
Other behaviours to know
Integer division still applies. With Integer x = 10; Integer y = 3; the expression x / y unboxes both and divides as ints, giving 3. Storing it in a double gives 3.0, not 3.3333. To get the decimals, cast one operand first: (double) x / y gives 3.3333333333333335. All three were run.
Immutability. In Integer n = 5; Integer m = n; n++; the n++ unboxes n, adds one and boxes a new Integer 6 into n. The object that m refers to is untouched. The program printed 6 5.
null. A wrapper variable is a reference, so it can hold null; an int cannot. Unboxing null throws a NullPointerException, which was confirmed with Integer z = null; int w = z;.
Comparing. == on two wrapper variables compares references, not values. Integer big1 = 1000, big2 = 1000; gave big1 == big2 false and big1.equals(big2) true when run. Java happens to reuse objects for small values (100 compared true), which hides the bug in small tests. When you want to compare the values of two wrapper objects, use equals, or unbox one to a primitive first.
Range. Integer.MAX_VALUE is 2147483647 and Integer.MIN_VALUE is -2147483648, as printed by the run. An int has no NaN or infinity, so the usual way to start a minimum search is Integer.MAX_VALUE.
Common misconceptions
- “Autoboxing can change
inttoDouble.” It cannot.Double d = 7;is a compile-time error. - “An Integer can be changed in place.” Wrapper objects are immutable.
n++points the variable at a new object. - “
parseIntis called on a String, likes.parseInt().” It is a class method:Integer.parseInt(s). - “
parseIntaccepts any number-like text.” It accepts only an optional sign and digits."4.0"and" 5"fail. - “
==is fine for Integer values.” It tests whether the two variables refer to the same object. - “An
Integeris always safe to use as anint.” If it isnull, unboxing throwsNullPointerException. - “
Integerandintbehave identically in division.” Division of twoIntegers unboxes both and uses integer division.
Where this leads
Wrapper classes are what make ArrayList<Integer> and ArrayList<Double> possible, so they unlock Topic 4.8 and the list algorithms of Topic 4.10. They also finish the file-reading pattern of Topic 4.6, since split and parseInt turn text into data. Work the Topic 4.7 practice questions, recap with the revision notes, and use the checklist. Next: the Topic 4.8 study guide on ArrayList methods.
Quick questions
Why does Java have both int and Integer?
Some Java features, such as the ArrayList class, can only hold objects. A wrapper class lets a primitive value travel inside an object. Primitives are simpler and faster, so Java keeps both.
Do I need to import Integer or Double?
No. Integer and Double are in the java.lang package, which Java makes available automatically.
What does immutable mean for an Integer?
Once an Integer object is created, its value cannot be changed. An expression such as n++ on an Integer variable makes the variable refer to a different Integer object; it does not alter the old one.
What happens if parseInt gets a String that is not a whole number?
The method throws a NumberFormatException at run time. Values such as "12a", "4.0" and " 5" (with a space) all cause it.