Learn Java

Lesson 32Phase 4: Collections and algorithms

Wrapper classes and autoboxing

Integer, Double and friends: why collections need them, how Java boxes values for you, and the traps that come with it.

By Updated 40 min

  • Integer
  • Double
  • autoboxing
  • unboxing
  • Integer cache
  • parseInt

You can write ArrayList<Integer> but not ArrayList<int>. A collection stores objects, and an int is a plain value, not an object. Java solves this with a wrapper class for each primitive type: a small class whose objects each hold one value. This lesson shows how values move in and out of those objects, how much of that Java does without being asked, and the bugs that appear when you forget the objects are there.

By the end of this lesson
  • Say why a collection cannot hold an int, and name the wrapper class of each of the eight primitive types.
  • Box a value by hand with valueOf, unbox it with intValue, and spot the places where Java does both for you.
  • Predict what == says about two Integer objects, explain the Integer cache, and compare wrapper values with equals.
  • Explain the NullPointerException that comes from unboxing null, and prevent it.
  • Turn text into numbers with parseInt, parseDouble and valueOf, in base 10 or another base, and read a NumberFormatException.
  • Use MIN_VALUE, MAX_VALUE and the helper methods on Integer and Character, and keep boxing out of busy loops.
From before
In ArrayList: a list that can grow you wrote ArrayList<Integer>, met the words autoboxing and unboxing, and saw that remove(3) on a list of numbers removes a position. The references lesson showed that a variable of an object type holds a reference, that ==on two objects asks “the same object?”, and that equals is meant to compare contents. Static methods and constants come from static and final, and the last lesson introduced static factory methods, which may hand back an object they made earlier. Integer.valueOf is one of those.

An int is not an object#

Say you are building a music app and want to keep the length of every song in a playlist, in seconds. A list fits the job, and the lengths are whole numbers, so ArrayList<int> looks right. The compiler refuses it with unexpected type, as you saw in the ArrayList lesson, and asks for a reference type instead.

The reason is the way each kind of value is stored. An int variable holds the number itself. A variable of an object type holds a reference: an arrow to an object that lives in a separate area of memory called the heap. A collection is built to store references, one per element, so a bare number has nowhere to go.

So every primitive type has a partner class. An object of that class holds one value of the primitive type and nothing else, the way a parcel box holds one item so that it can travel through a system built for boxes. These classes are called wrapper classes, because each object wraps one value. Putting a value into its object is called boxing, and taking it back out is called unboxing.

Primitive typeWrapper classBoxing a value by hand
byteByteByte.valueOf((byte) 7)
shortShortShort.valueOf((short) 300)
intIntegerInteger.valueOf(42)
longLongLong.valueOf(42L)
floatFloatFloat.valueOf(1.5f)
doubleDoubleDouble.valueOf(4.99)
charCharacterCharacter.valueOf('A')
booleanBooleanBoolean.valueOf(true)
Six wrapper names are the primitive's name with a capital letter. Integer and Character are the two that differ.

All eight live in java.lang, the package every Java file can use without an import line. Their objects are immutable, like strings: once a wrapper object exists, the value inside it never changes. Most programs use Integer, Double, Long, Boolean and Character. Byte, Short and Float are rare.

Key idea

A wrapper object holds one primitive value, so that the value can go where only objects are allowed, such as into a collection.

Why the angle brackets only take classes
The next lesson, generics, shows how a class like ArrayList is written once to hold any type of object, and why the type you put in the angle brackets must be an object type, never a primitive.

Boxing and unboxing by hand#

Before Java 5, released in 2004, you wrote every conversion yourself. Doing it the long way once shows you what Java now does behind the scenes. Each wrapper class has a static method, valueOf, that boxes a value, and an instance method that unboxes it again: intValue() on an Integer, doubleValue() on a Double, booleanValue() on a Boolean, and so on.

Playlist.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 int seconds = 215;
6 Integer boxed = Integer.valueOf(seconds);
7 int back = boxed.intValue();
8
9 ArrayList<Integer> lengths = new ArrayList<>();
10 lengths.add(Integer.valueOf(185));
11 lengths.add(boxed);
12
13 int first = lengths.get(0).intValue();
14 System.out.println("Playlist: " + lengths);
15 System.out.println("Total seconds: " + (first + back));
16 }
17}
Prints
›Playlist: [185, 215]
›Total seconds: 400

It prints Playlist: [185, 215] and then Total seconds: 400. Integer.valueOf(seconds) builds an Integer object that holds 215, and boxed.intValue() reads the number back out into a plain int. The list only ever receives Integer objects, and lengths.get(0) hands one back, so the code calls intValue() on it before doing any arithmetic. As you step through, compare the type shown next to each variable: boxed is an Integer and back is an int, yet both show 215.

Why valueOf and not new#

Older code builds wrapper objects with a constructor, as in new Integer(215). That constructor has been deprecated since Java 9: it still works, but the documentation advises against it, and the compiler reminds you. Here is what javac prints for a file whose line 3 is Integer boxed = new Integer(215);:

Terminal
read along
1$ javac Main.java
2Note: Main.java uses or overrides a deprecated API.
3Note: Recompile with -Xlint:deprecation for details.
4$ javac -Xlint:deprecation Main.java
5Main.java:3: warning: [deprecation] Integer(int) in Integer has been deprecated
6 Integer boxed = new Integer(215);
7 ^
81 warning

A warning does not stop the program from compiling, but it is worth acting on. new always builds a fresh object. valueOf is a static factory method, so it is free to hand back an object it made earlier, and for small numbers it does. That difference decides what == says about two boxed numbers, as you will see shortly. Use valueOf, or let Java box for you.

Autoboxing: Java does it for you#

Since Java 5, the compiler writes those calls for you. Wherever an int sits where an Integer is needed, it inserts Integer.valueOf(...). That is called autoboxing. Wherever an Integer sits where an int is needed, it inserts .intValue(), which is called auto-unboxing, or just unboxing. Here is the playlist again, with every conversion left to Java:

Playlist.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 Integer boxed = 215;
6 int back = boxed;
7
8 ArrayList<Integer> lengths = new ArrayList<>();
9 lengths.add(185);
10 lengths.add(boxed);
11
12 int first = lengths.get(0);
13 System.out.println("Playlist: " + lengths);
14 System.out.println("Total seconds: " + (first + back));
15 }
16}
Prints
›Playlist: [185, 215]
›Total seconds: 400

The output is the same, and so is the work done while it runs. The compiler turned Integer boxed = 215; into Integer boxed = Integer.valueOf(215); and int back = boxed; into int back = boxed.intValue();. The objects are all still there. You just no longer see the code that makes and opens them.

You writeJava runsWhat happens
Integer n = 42;Integer n = Integer.valueOf(42);Boxing, to store into an Integer variable
int x = n;int x = n.intValue();Unboxing, to store into an int variable
list.add(7);list.add(Integer.valueOf(7));Boxing an argument for a method that wants an object
int y = n + 1;int y = n.intValue() + 1;Unboxing for arithmetic
if (n > 40)if (n.intValue() > 40)Unboxing for a comparison
n++;n = Integer.valueOf(n.intValue() + 1);Unbox, add, box a new Integer
for (int s : list)Each element is unboxed with intValue()Unboxing on the way out of a list
None of the middle column appears in your file. The compiler writes those calls into the compiled class.

The n++ row is easy to miss. A wrapper object never changes, so n++ cannot add one to the number inside it. It unboxes, adds one, boxes the result into a different Integer, and points n at that. Any other variable that pointed at the old object still sees the old number.

Boxing only fills its own wrapper#

An int boxes into an Integer, a double into a Double, and so on down the table. Java will not first turn an int into a long or a double and then box it, so an int literal cannot become a Long or a Double. Both of these lines fail to compile:

Terminal
read along
1$ javac Main.java
2Main.java:3: error: incompatible types: int cannot be converted to Long
3 Long total = 0;
4 ^
5Main.java:4: error: incompatible types: int cannot be converted to Double
6 Double price = 5;
7 ^
82 errors

The fix is to start from a value of the right type: Long total = 0L; and Double price = 5.0;. The L makes the literal a long, as you saw in variables and types.

Quick check
Which of these lines fails to compile?

Java boxes only when it must#

When a method comes in two versions, one taking an int and one taking an object, Java picks the version that needs no boxing. You met the best-known case in the ArrayList lesson: on a list of numbers, remove(2) calls remove(int) and removes the item at index 2, while remove(Integer.valueOf(2)) calls remove(Object) and removes the value 2.

Quick check
The list ages holds [30, 2, 45]. What does ages.remove(2); followed by System.out.println(ages); print?
Key idea

Autoboxing is the compiler writing valueOf and intValue for you. The calls disappear from your code, but the objects they make are still there.

Comparing wrappers: the Integer cache#

Two Integer variables hold the same number. Does == say they are equal? Decide before you run it.

Predict the outputCache.java
1public class Main {
2 public static void main(String[] args) {
3 Integer a = 127;
4 Integer b = 127;
5 System.out.println(a == b);
6
7 Integer c = 128;
8 Integer d = 128;
9 System.out.println(c == d);
10 }
11}

What do the two lines print?

It prints true and then false. The two halves are the same code, and the only difference is 127 against 128. Two facts explain it.

First, == on two objects does not look at the numbers inside. It asks whether both variables point at the same object, exactly as it did for your own classes in the references lesson.

Second, Integer a = 127; runs Integer.valueOf(127), and valueOf keeps a store of ready-made Integer objects, one for every number from -128 to 127. This store is called the Integer cache. Ask valueOf for one of those numbers and you get the object from the store, the same one every time. Ask for any other number and it builds a new object.

So a and b both point at the one cached object for 127, and a == b is true. c and d each got a brand-new object holding 128. Two objects means c == d is false, even though both hold the same number. Try other pairs in the lab: pick two values, switch b between Integer and int, and follow the arrows.

Integer cache labInteger.valueOf
Integer a =
Integer b =
Declare b as
Try
Integer a = 127; // from the cache
Integer b = 127; // from the cache
VariablesNew objectsInteger cache-128 to 127Integer aInteger bInteger127
a == b
true
asks: the same object?
a.equals(b)
true
compares the numbers inside

127 is inside the cache range, so both calls to Integer.valueOf(127) handed back the same ready-made object. a and b point at one object, so == says true.

The cache exists because small numbers are everywhere: counters, indexes, ages, months, scores in a game. Sharing one object for each of them saves building thousands of identical ones. The sharing is safe only because an Integer never changes, the same reason Java can share one copy of each string literal in the string pool you met in the strings lesson.

Why this bug hides so well
Code that compares Integer objects with == passes every test that uses small numbers. It starts failing only when real data goes past 127: order quantities, prices in cents, player scores. Never compare two wrapper objects with ==.

Comparing the numbers instead#

There are two safe ways to compare the numbers: call equals, or unbox first and compare two primitives. Run this and match each line of output to its line of code.

Compare.java
1public class Main {
2 public static void main(String[] args) {
3 Integer c = 128;
4 Integer d = 128;
5
6 System.out.println(c == d);
7 System.out.println(c.equals(d));
8 System.out.println(c.intValue() == d.intValue());
9
10 int e = 128;
11 System.out.println(c == e);
12 System.out.println(c <= d && c >= d);
13 }
14}
Prints
›false
›true
›true
›true
›true
  • c == d is false: two separate objects.
  • c.equals(d) is true: the equals method of Integer compares the numbers inside.
  • c.intValue() == d.intValue() is true: after unboxing there are two plain int values, and == on primitives compares values.
  • c == e is true: when one side of == is a primitive, Java unboxes the other side and compares two numbers.
  • c <= d && c >= d is true: <, >, <= and >= mean nothing for objects, so Java always unboxes for them. That leaves the odd result that c is both at most and at least d, yet not == to it.
Key idea

==on two wrapper objects asks “the same object?”, and the Integer cache makes the answer depend on the number. Compare wrapper values with equals, or unbox them first.

ExerciseTies.java
A quiz game keeps scores in an ArrayList<Integer> and counts how many players share the top score. Three players scored 250, but the program reports 1. Fix the comparison so it compares the scores.

Where equals still surprises#

equals is the right tool for wrapper objects, with two catches. The first comes from mixing wrapper classes. An online shop stores its order numbers as Long values, which is common, because a long can count far past the two billion an int allows.

Predict the outputOrderIds.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<Long> orderIds = new ArrayList<>();
6 orderIds.add(1001L);
7 orderIds.add(1002L);
8
9 System.out.println(orderIds.contains(1002L));
10 System.out.println(orderIds.contains(1002));
11
12 Integer one = 1;
13 System.out.println(one.equals(1L));
14 }
15}

What do the three lines print? Type one line of output per line.

It prints true, false, false. The literal 1002 has no L, so it is an int, and contains needs an object, so Java boxes it into an Integer. An Integer is never equal to a Long, even when both hold the same number: the equals method of Long first checks that the other object is a Long, and the one in Integer checks for an Integer. The last line fails for the same reason, since 1L boxes into a Long. The code compiles without a warning and quietly answers false. Make both sides the same type: orderIds.contains(1002L).

Two odd doubles#

The second catch involves two special double values. NaN, short for “not a number”, is what 0.0 / 0.0 gives. Dividing a double by zero never crashes the way whole-number division does; it gives a special value such as this one. NaN is the one value that is not == to itself. Java also has a negative zero, -0.0, which == treats as equal to 0.0. The equals method of Double disagrees with == about both:

OddDoubles.java
1public class Main {
2 public static void main(String[] args) {
3 double nan = 0.0 / 0.0;
4 Double boxedNan = nan;
5 System.out.println(nan == nan);
6 System.out.println(boxedNan.equals(nan));
7
8 Double zero = 0.0;
9 System.out.println(0.0 == -0.0);
10 System.out.println(zero.equals(-0.0));
11 }
12}
Prints
›false
›true
›true
›false

The output is false, true, true, false. Double's equals compares the stored bit patterns of the two numbers, with every NaN counted as one pattern. That makes a boxed NaN equal to NaN, so a NaN put into a list can still be found there. 0.0 and -0.0 have different patterns, and they do behave differently: 1.0 / 0.0 is Infinity, while 1.0 / -0.0 is -Infinity.

Quick check
A calculation stores its result in double x. Which test tells you whether x is NaN?
Key idea

Compare wrapper objects with equals, and give both sides the same wrapper class. A Long never equals an Integer, whatever the numbers.

Unboxing null#

A variable of a wrapper type holds a reference, so it can hold null, which an int never can. Fields show the difference: an int field starts at 0, while an Integer field starts at null, following the default values from creating classes and objects.

Sometimes nullis exactly what you want. In a game, a best score of 0 is a real score, and “has not played yet” is something else. An Integer field can say both: a number, or null for no games yet. Trouble starts when that null reaches code that needs a number. This Player keeps its best score, and finishGame replaces it whenever a new score beats it. Step through it.

BestScore.java
1public class Main {
2 public static void main(String[] args) {
3 Player ana = new Player();
4 System.out.println("Best before any game: " + ana.bestScore);
5
6 ana.finishGame(42);
7 ana.finishGame(17);
8 System.out.println("Best after two games: " + ana.bestScore);
9 }
10}
11
12class Player {
13 Integer bestScore; // null until the first game
14
15 void finishGame(int score) {
16 if (score > bestScore) {
17 bestScore = score;
18 }
19 }
20}
Prints
›Best before any game: null
!Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "this.bestScore" is null
! at Player.finishGame(Main.java:16)
! at Main.main(Main.java:6)
Stops on line 16

NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "this.bestScore" is null

A variable held null, so there was no object to use.

The first line prints Best before any game: null. Printing null is fine, because joining it to a string turns it into the text null. The crash comes on line 16, during the first game. score > bestScore needs a number, so Java unboxes bestScore by calling bestScore.intValue(), and there is no object to call it on. Java stops with this message:

NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "this.bestScore" is null

Read it slowly. It names intValue(), a method that appears nowhere in your code. Autoboxing wrote that call, and this.bestScore is the field that held null. Whenever a NullPointerException mentions intValue(), doubleValue() or booleanValue() on a line where you never called one, look for a wrapper that was null. The parts of the message are explained in reading errors and debugging.

The fix is to deal with the empty case before anything unboxes. The first game is always a new best, so test for null first:

BestScore.java
1public class Main {
2 public static void main(String[] args) {
3 Player ana = new Player();
4 System.out.println("Best before any game: " + ana.bestScore);
5
6 ana.finishGame(42);
7 ana.finishGame(17);
8 System.out.println("Best after two games: " + ana.bestScore);
9 }
10}
11
12class Player {
13 Integer bestScore; // null until the first game
14
15 void finishGame(int score) {
16 if (bestScore == null || score > bestScore) {
17 bestScore = score;
18 }
19 }
20}
Prints
›Best before any game: null
›Best after two games: 42

Now it prints Best after two games: 42. In the first game bestScore == null is true, and || skips its right side when the left side is already true, so score > bestScore never runs while the field is empty. That skipping is the short-circuit rule from operators. The order matters: written the other way round, the comparison would run first and crash again.

Two ways to stay safe
Check for null before you unbox, as finishGame now does. Or, when a value can never be missing, use the primitive type: an int field can never be null, so it can never cause this crash.
Where else a null wrapper comes from
A list can hold null items, so for (int m : marks) crashes the same way on the first null it unboxes. A HashMap returns null when you ask for a key it does not have, which you will meet in HashMap and HashSet. Optionallater gives you a clearer way to say “there may be no value”.

Turning text into numbers#

Numbers often arrive as text: typed into a form, read from a file, or passed on the command line. The text "14" is two characters, and adding 1 to it joins on a third. The wrapper classes have static methods that read a number out of a String.

FormInput.java
1public class Main {
2 public static void main(String[] args) {
3 String typedAge = "14";
4 String typedHeight = "1.75";
5
6 int age = Integer.parseInt(typedAge);
7 double height = Double.parseDouble(typedHeight);
8 Integer boxedAge = Integer.valueOf(typedAge);
9
10 System.out.println(typedAge + 1);
11 System.out.println(age + 1);
12 System.out.println(height * 100);
13 System.out.println(boxedAge.equals(age));
14 }
15}
Prints
›141
›15
›175.0
›true

The first line prints 141, because + with a String joins text. After parsing, age + 1 is 15, height * 100 is 175.0, and the last line is true. The naming follows one rule: a parse method gives you a primitive, and valueOf gives you a wrapper object. Integer.valueOf(text) reads the number exactly as parseInt does and then boxes it, using the cache. Reach for parseInt unless you need the object.

CallGives youNotes
Integer.parseInt("42")the int 42An optional + or - sign, then digits, nothing else
Long.parseLong("9000000000")the long 9000000000For numbers too big for an int
Double.parseDouble("4.99")the double 4.99Also reads forms such as 1e3, which is 1000.0
Boolean.parseBoolean("TRUE")the boolean trueOnly the word true, in any mix of capitals, gives true. Even "yes" gives false.
Integer.valueOf("42")an Integer object holding 42Parses, then boxes
String.valueOf(42)the String "42"The way back to text. Integer.toString(42) does the same.

When the text is not a number#

parseInt accepts an optional + or - sign followed by digits, and nothing else: no spaces, no decimal point, no commas. Anything else makes it throw a NumberFormatException, which you met in reading errors and debugging. A single space is enough:

Spaces.java
1public class Main {
2 public static void main(String[] args) {
3 String typed = " 42";
4 System.out.println("Read: [" + typed + "]");
5 int n = Integer.parseInt(typed);
6 System.out.println("Doubled: " + n * 2);
7 }
8}
Prints
›Read: [ 42]
!Exception in thread "main" java.lang.NumberFormatException: For input string: " 42"
! at Main.main(Main.java:5)
Stops on line 5

NumberFormatException: For input string: " 42"

The text was not a number Java could read, such as "abc" or "12.5" for an int.

The program stops on line 5 with NumberFormatException: For input string: " 42". The quotation marks around the text in the message show the space at the start, which is easy to miss otherwise. The fix is to tidy the text first with trim() from the strings lesson: Integer.parseInt(typed.trim()). Double.parseDouble is more forgiving and ignores spaces at both ends on its own.

A number that is too big for an int fails the same way: Integer.parseInt("3000000000") throws, because the largest int is 2147483647. Use Long.parseLong for numbers that size.

Quick check
Which call returns the int 7 without throwing an exception?
Recovering instead of crashing
A program that reads input should not stop on a typo. In exception handling you will learn to catch a NumberFormatException and ask again. Until then you can check the text before parsing it, which the exercise later in this lesson asks you to do.

Numbers in other bases#

parseInt takes an optional second argument, the radix: the base the digits are written in. Base 10 is the default. Base 2 uses only the digits 0 and 1, and base 16 uses 0 to 9 and then the letters a to f for ten to fifteen. Colors on web pages are a common place to meet base 16: #FF8800 is three pairs of base-16 digits, one each for red, green and blue.

HexColor.java
1public class Main {
2 public static void main(String[] args) {
3 String color = "#FF8800";
4
5 int red = Integer.parseInt(color.substring(1, 3), 16);
6 int green = Integer.parseInt(color.substring(3, 5), 16);
7 int blue = Integer.parseInt(color.substring(5, 7), 16);
8 System.out.println("red " + red + ", green " + green + ", blue " + blue);
9
10 System.out.println(Integer.parseInt("1010", 2));
11 System.out.println(Integer.toBinaryString(10));
12 System.out.println(Integer.toHexString(136));
13 }
14}
Prints
›red 255, green 136, blue 0
›10
›1010
›88

It prints red 255, green 136, blue 0, then 10, 1010 and 88. substring cuts out each pair, and parseInt(..., 16) reads it in base 16: FF is 15 * 16 + 15, which is 255, and 88 is 8 * 16 + 8, which is 136. Capital and small letters both work. Going the other way, Integer.toBinaryString, Integer.toHexString and Integer.toString(n, radix) write a number out in another base. A digit that does not belong to the base fails with a message that names the base: Integer.parseInt("12z", 16) throws NumberFormatException: For input string: "12z" under radix 16.

Constants and helper methods#

The smallest and largest values#

Each numeric wrapper class has two constants, MIN_VALUE and MAX_VALUE, the limits of its primitive type. They are static final fields, like Math.PI.

Limits.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println(Integer.MAX_VALUE);
4 System.out.println(Integer.MIN_VALUE);
5 System.out.println(Integer.MAX_VALUE + 1);
6 System.out.println(Long.MAX_VALUE);
7 System.out.println(Double.MIN_VALUE);
8 }
9}
Prints
›2147483647
›-2147483648
›-2147483648
›9223372036854775807
›4.9E-324

The first two lines are the limits of an int: 2147483647 and -2147483648, a little over two billion either way. The third line shows what happens when you go past the top. There is no error: the value wraps around to the most negative int. This is called overflow, and Java does not warn you when it happens. Long.MAX_VALUE is 9223372036854775807, about nine billion billion.

Double.MIN_VALUE is not the most negative double
The last line printed 4.9E-324, a tiny positive number. Double.MIN_VALUE is the smallest positive double, the one closest to zero. The most negative double is -Double.MAX_VALUE.

Integer.MAX_VALUE makes a good starting point when you search for the smallest value. No int is bigger, so the lowest-so-far can only go down from there, and any real reading below it takes its place straight away.

Put the lines in orderColdest.java
A weather station sends its readings as text. Put the lines in order so the program parses each reading and prints Lowest: -11.

Tap a line, then tap where it should go. Or use the arrows.

Static helpers on Integer and Character#

The wrapper classes also carry static methods that work on plain primitives. These are the ones you will use most:

MethodWhat it gives you
Integer.compare(a, b)A negative number if a is smaller, 0 if they are equal, a positive number if a is bigger
Integer.max(a, b), Integer.min(a, b), Integer.sum(a, b)The same as Math.max, Math.min and a + b, written as methods so you can pass them around: Integer::sum is a method reference that adds two numbers
Integer.toBinaryString(n), Integer.toHexString(n)The number written out in base 2 or base 16
Character.isDigit(c), Character.isLetter(c)Whether the char is a digit, and whether it is a letter
Character.isLetterOrDigit(c), Character.isWhitespace(c)Whether it is a letter or a digit, and whether it is a space, tab or newline
Character.isUpperCase(c), Character.toUpperCase(c)Whether it is a capital letter, and the capital version of it
Character.getNumericValue('7')The digit's value as an int, here 7

A sign-up form wants to know how many letters and digits a password has:

Password.java
1public class Main {
2 public static void main(String[] args) {
3 String password = "tiger42";
4 int letters = 0;
5 int digits = 0;
6 for (int i = 0; i < password.length(); i++) {
7 char c = password.charAt(i);
8 if (Character.isLetter(c)) {
9 letters++;
10 } else if (Character.isDigit(c)) {
11 digits++;
12 }
13 }
14 System.out.println(letters + " letters, " + digits + " digits");
15 System.out.println(Integer.compare(letters, digits));
16 System.out.println(Integer.max(letters, digits));
17 }
18}
Prints
›5 letters, 2 digits
›1
›5

It prints 5 letters, 2 digits, then 1 and 5. Integer.compare(5, 2) is positive because 5 is bigger. Java prints 1 here, but the documentation promises only the sign, so test the result with > 0, never with == 1. Integer.max picks the larger count.

ExerciseValidNumbers.java
A form sends you whatever people typed. Finish isWholeNumber so it returns true only for an optional minus sign followed by one or more digits, with nothing else. Then main parses only the valid entries, so it never crashes. The program should print Total: 37 and Skipped: 4.

What boxing costs#

Every boxed value outside the cache is a new object, and each object takes time to build, memory to hold, and work from the garbage collector to clear away later. One box is nothing. One box on every pass of a busy loop adds up. Step through this and look at the type of total:

Steps.java
1public class Main {
2 public static void main(String[] args) {
3 int[] steps = {4200, 6100, 3900, 8000, 7300};
4
5 Long total = 0L;
6 for (int s : steps) {
7 total += s;
8 }
9 System.out.println("Steps over five days: " + total);
10 }
11}
Prints
›Steps over five days: 29500

It prints Steps over five days: 29500, the right answer. But total is a Long, and a Long object cannot change, so total += s has to run total = Long.valueOf(total.longValue() + s); on every pass: unbox, add, then box the sum into a new Long. After the first pass the total is far past 127, so each pass makes a new object and drops the previous one. Five days cost five objects. A year of step counts taken every minute is 525,600 passes, and as many objects, all to hold one running total. Declaring long total = 0; instead keeps the total in a single primitive variable.

Choosing between the two kinds of type comes down to whether you need an object:

Use a primitive: int, double...
Use a wrapper: Integer, Double...
Local variables, loop counters, totals, and anything you do arithmetic on.
Items in a collection, such as ArrayList<Integer>, because collections hold only objects.
Fields that always have a value, such as an age or a quantity.
Fields where null means “no value yet”, like a best score before the first game.
Everywhere else. It is the default choice.
Only where you need an object or a null.

Try it

Answer each one in your head or on paper first, then open the answer.

1
Write these two lines the long way, with every call that Java adds for you: Integer price = 250; and int doubled = price * 2;
2
300 is outside the Integer cache. What do the two lines print, and why is the first one not false?
3
A class has the field Integer stock; and nothing ever sets it. What happens when a method runs int left = stock - 1;, and which word in the error message points at unboxing?
4
Integer.parseInt("1,500") throws a NumberFormatException. Why, and how can you read the number anyway?
5
Work out all three lines by hand, then run the code to check.
6
Two programs both add up a million numbers and print the same total. One declares Long total = 0L; and the other long total = 0;. Why can the first one be slower?

Practice

Work through these on your own. Each one builds on this page.

1
Write a method int[] toRgb(String color) that turns a color such as "#1E90FF" into its three numbers, and a method String toHex(int red, int green, int blue) that turns three numbers from 0 to 255 back into a color string. Check that toHex(30, 144, 255) gives "#1e90ff".
2
Try the cache in a real terminal. Write a loop from -130 to 130 that boxes each number twice with Integer.valueOf and counts how many pairs are ==. Predict the count, run it with java Main.java, then run it again with java -XX:AutoBoxCacheMax=1000 Main.java and explain why the count changes.
3
Write a Thermostat class with an Integer field target that stays null until someone sets it. Add String describe(), which returns No target set or Target: 21 degrees, and void warmer(), which raises the target by one degree, or sets it to 20 when there is no target yet. Test every method on a thermostat that has never been set, and make sure nothing ever unboxes null.
4
A teammate's code compared two Integer scores with ==. It passed every test, then failed in the first week of real use. Write one short paragraph explaining what happened, using the words “object”, “cache” and valueOf, and say what the line should have been.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email