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 BasithUpdated 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.
- 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 withintValue, and spot the places where Java does both for you. - Predict what
==says about twoIntegerobjects, explain the Integer cache, and compare wrapper values withequals. - Explain the
NullPointerExceptionthat comes from unboxingnull, and prevent it. - Turn text into numbers with
parseInt,parseDoubleandvalueOf, in base 10 or another base, and read aNumberFormatException. - Use
MIN_VALUE,MAX_VALUEand the helper methods onIntegerandCharacter, and keep boxing out of busy loops.
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 type | Wrapper class | Boxing a value by hand |
|---|---|---|
byte | Byte | Byte.valueOf((byte) 7) |
short | Short | Short.valueOf((short) 300) |
int | Integer | Integer.valueOf(42) |
long | Long | Long.valueOf(42L) |
float | Float | Float.valueOf(1.5f) |
double | Double | Double.valueOf(4.99) |
char | Character | Character.valueOf('A') |
boolean | Boolean | Boolean.valueOf(true) |
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 ideaA wrapper object holds one primitive value, so that the value can go where only objects are allowed, such as into a collection.
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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { int seconds = 215; Integer boxed = Integer.valueOf(seconds); int back = boxed.intValue(); ArrayList<Integer> lengths = new ArrayList<>(); lengths.add(Integer.valueOf(185)); lengths.add(boxed); int first = lengths.get(0).intValue(); System.out.println("Playlist: " + lengths); System.out.println("Total seconds: " + (first + back)); }}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);:
$ javac Main.javaNote: Main.java uses or overrides a deprecated API.Note: Recompile with -Xlint:deprecation for details.$ javac -Xlint:deprecation Main.javaMain.java:3: warning: [deprecation] Integer(int) in Integer has been deprecated Integer boxed = new Integer(215); ^1 warningA 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:
import java.util.ArrayList; public class Main { public static void main(String[] args) { Integer boxed = 215; int back = boxed; ArrayList<Integer> lengths = new ArrayList<>(); lengths.add(185); lengths.add(boxed); int first = lengths.get(0); System.out.println("Playlist: " + lengths); System.out.println("Total seconds: " + (first + back)); }}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 write | Java runs | What 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 |
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:
$ javac Main.javaMain.java:3: error: incompatible types: int cannot be converted to Long Long total = 0; ^Main.java:4: error: incompatible types: int cannot be converted to Double Double price = 5; ^2 errorsThe 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.
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.
ages holds [30, 2, 45]. What does ages.remove(2); followed by System.out.println(ages); print?Key ideaAutoboxing is the compiler writing
valueOfandintValuefor 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.
public class Main { public static void main(String[] args) { Integer a = 127; Integer b = 127; System.out.println(a == b); Integer c = 128; Integer d = 128; System.out.println(c == d); }}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.
- 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.
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.
public class Main { public static void main(String[] args) { Integer c = 128; Integer d = 128; System.out.println(c == d); System.out.println(c.equals(d)); System.out.println(c.intValue() == d.intValue()); int e = 128; System.out.println(c == e); System.out.println(c <= d && c >= d); }}c == disfalse: two separate objects.c.equals(d)istrue: theequalsmethod ofIntegercompares the numbers inside.c.intValue() == d.intValue()istrue: after unboxing there are two plainintvalues, and==on primitives compares values.c == eistrue: when one side of==is a primitive, Java unboxes the other side and compares two numbers.c <= d && c >= distrue:<,>,<=and>=mean nothing for objects, so Java always unboxes for them. That leaves the odd result thatcis both at most and at leastd, 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 withequals, or unbox them first.
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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<Long> orderIds = new ArrayList<>(); orderIds.add(1001L); orderIds.add(1002L); System.out.println(orderIds.contains(1002L)); System.out.println(orderIds.contains(1002)); Integer one = 1; System.out.println(one.equals(1L)); }}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:
public class Main { public static void main(String[] args) { double nan = 0.0 / 0.0; Double boxedNan = nan; System.out.println(nan == nan); System.out.println(boxedNan.equals(nan)); Double zero = 0.0; System.out.println(0.0 == -0.0); System.out.println(zero.equals(-0.0)); }}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.
double x. Which test tells you whether x is NaN?Key ideaCompare wrapper objects with
equals, and give both sides the same wrapper class. ALongnever equals anInteger, 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.
public class Main { public static void main(String[] args) { Player ana = new Player(); System.out.println("Best before any game: " + ana.bestScore); ana.finishGame(42); ana.finishGame(17); System.out.println("Best after two games: " + ana.bestScore); }} class Player { Integer bestScore; // null until the first game void finishGame(int score) { if (score > bestScore) { bestScore = score; } }}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:
public class Main { public static void main(String[] args) { Player ana = new Player(); System.out.println("Best before any game: " + ana.bestScore); ana.finishGame(42); ana.finishGame(17); System.out.println("Best after two games: " + ana.bestScore); }} class Player { Integer bestScore; // null until the first game void finishGame(int score) { if (bestScore == null || score > bestScore) { bestScore = score; } }}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.
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.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.
public class Main { public static void main(String[] args) { String typedAge = "14"; String typedHeight = "1.75"; int age = Integer.parseInt(typedAge); double height = Double.parseDouble(typedHeight); Integer boxedAge = Integer.valueOf(typedAge); System.out.println(typedAge + 1); System.out.println(age + 1); System.out.println(height * 100); System.out.println(boxedAge.equals(age)); }}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.
| Call | Gives you | Notes |
|---|---|---|
Integer.parseInt("42") | the int 42 | An optional + or - sign, then digits, nothing else |
Long.parseLong("9000000000") | the long 9000000000 | For numbers too big for an int |
Double.parseDouble("4.99") | the double 4.99 | Also reads forms such as 1e3, which is 1000.0 |
Boolean.parseBoolean("TRUE") | the boolean true | Only the word true, in any mix of capitals, gives true. Even "yes" gives false. |
Integer.valueOf("42") | an Integer object holding 42 | Parses, 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:
public class Main { public static void main(String[] args) { String typed = " 42"; System.out.println("Read: [" + typed + "]"); int n = Integer.parseInt(typed); System.out.println("Doubled: " + n * 2); }}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.
int 7 without throwing an exception?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.
public class Main { public static void main(String[] args) { String color = "#FF8800"; int red = Integer.parseInt(color.substring(1, 3), 16); int green = Integer.parseInt(color.substring(3, 5), 16); int blue = Integer.parseInt(color.substring(5, 7), 16); System.out.println("red " + red + ", green " + green + ", blue " + blue); System.out.println(Integer.parseInt("1010", 2)); System.out.println(Integer.toBinaryString(10)); System.out.println(Integer.toHexString(136)); }}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.
public class Main { public static void main(String[] args) { System.out.println(Integer.MAX_VALUE); System.out.println(Integer.MIN_VALUE); System.out.println(Integer.MAX_VALUE + 1); System.out.println(Long.MAX_VALUE); System.out.println(Double.MIN_VALUE); }}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.
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.
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:
| Method | What 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:
public class Main { public static void main(String[] args) { String password = "tiger42"; int letters = 0; int digits = 0; for (int i = 0; i < password.length(); i++) { char c = password.charAt(i); if (Character.isLetter(c)) { letters++; } else if (Character.isDigit(c)) { digits++; } } System.out.println(letters + " letters, " + digits + " digits"); System.out.println(Integer.compare(letters, digits)); System.out.println(Integer.max(letters, digits)); }}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.
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:
public class Main { public static void main(String[] args) { int[] steps = {4200, 6100, 3900, 8000, 7300}; Long total = 0L; for (int s : steps) { total += s; } System.out.println("Steps over five days: " + total); }}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:
ArrayList<Integer>, because collections hold only objects.null means “no value yet”, like a best score before the first game.null.Try it
Answer each one in your head or on paper first, then open the answer.
Integer price = 250; and int doubled = price * 2;false?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?Integer.parseInt("1,500") throws a NumberFormatException. Why, and how can you read the number anyway?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.
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".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.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.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#
- Integer in the Java SE 25 API documentation, with valueOf and its cache, the rules of parseInt, and every constant and helper
- Boxing and unboxing conversion in the Java Language Specification, sections 5.1.7 and 5.1.8, including the rule behind the Integer cache
- Autoboxing and unboxing on dev.java, the official Java learning site
- Double in the Java SE 25 API documentation, whose opening section explains how == and equals treat NaN and -0.0
Spotted a mistake, or a part that did not make sense? Report it by email