Primitives, wrappers, and the == trap
Why int and Integer are not interchangeable, and the caching quirk that makes == look like it works.
After this lesson you can
- Tell a primitive type from its wrapper class
- Say what autoboxing does and where it costs something
- Explain why == on two Integer values sometimes works and sometimes does not
Java has eight primitive types — int, long, double, float,
boolean, char, byte, short — and each has a corresponding
wrapper class: Integer, Long, Double, and so on. A primitive is a
raw value; a wrapper is a real object, which is the only kind of thing
a generic collection can hold.
int a = 5; // a primitive value
Integer b = 5; // an Integer object — this is autoboxing
List<Integer> nums = new ArrayList<>();
nums.add(5); // autoboxed to Integer on the way in
int first = nums.get(0); // auto-unboxed back to int on the way out
List<int> does not compile — generics only work with reference types,
which is the entire reason autoboxing exists: it lets 5 be written
where an Integer is needed, and the compiler inserts the conversion.
Try it
import java.util.*; public class Solution { public static String describe() { List<Integer> nums = new ArrayList<>(); for (int i = 0; i < 3; i++) { nums.add(i); // each int autoboxed to a new Integer } int sum = 0; for (int n : nums) { sum += n; // each Integer auto-unboxed back to int } return "nums=" + nums + " sum=" + sum; }}The == trap
== on two primitives compares values. == on two objects — which
includes wrapper types — compares references: are these the exact
same object in memory.
Integer a = 127;
Integer b = 127;
a == b; // true
Integer c = 200;
Integer d = 200;
c == d; // false
Both pairs look identical. The difference is that boxing reuses cached
Integer objects for -128 to 127 — the language specification requires
that range, and a JVM flag can raise the upper end, so anything outside
it may or may not be a fresh object. 127 == 127 is comparing two
references to the same cached object; 200 == 200 is comparing two
separately allocated objects that happen to hold the same value. The
cache range is exactly wide enough to make a bug look like it works in a
quick test and fail once real numbers show up.
The fix is the same one every language with this trap needs: compare
wrapper objects with .equals(), not ==.
Integer c = 200;
Integer d = 200;
c.equals(d); // true — always, regardless of caching
Unboxed primitives never have this problem — int c = 200; int d = 200; c == d is true unconditionally, because there is no object identity
to compare, only the value.
Common mistakes
- Comparing two
Integers with==. It passes every test that uses small numbers and fails in production with an order id of 1000.
import java.util.*; public class Solution { public static List<String> describe() { List<String> out = new ArrayList<>(); Map<String, Integer> stock = new HashMap<>(); stock.put("pens", 3); try { int paper = stock.get("paper"); // get returns null, then unboxes it out.add("paper=" + paper); } catch (NullPointerException e) { out.add("unboxing null threw NullPointerException"); } List<Integer> ids = new ArrayList<>(List.of(10, 20, 30)); ids.remove(1); // remove(int index) out.add("after remove(1): " + ids); ids.remove(Integer.valueOf(10)); // remove(Object o) out.add("after remove(Integer.valueOf(10)): " + ids); return out; }}What interviewers ask
- Why can't you declare a
List<int>? Type arguments must be reference types; generics are erased toObjectat runtime, and a primitive is not anObject. Integer a = 127, b = 127; a == bis true, but with 128 it is false. Why?==compares references; boxing goes throughInteger.valueOf, which returns cached objects for -128 to 127 and may allocate new ones outside that range. Compare withequals.- What does autoboxing cost? A possible allocation per boxed value,
an extra indirection on every read, and a
NullPointerExceptionon unboxingnull. In a hot loop over millions of numbers, a primitive array orIntStreamavoids all three.
Try it yourself
2 visible tests · 3 hidden testsImplement countEqualPairs(values). values is a List<Integer>.
Count how many adjacent pairs (values[i], values[i + 1]) are equal in
value — use .equals(), not ==, so the answer is correct
whether or not Java happened to cache that particular number.
countEqualPairs([1,1,2,3,3,3])countEqualPairs([200,200,300])
Sign up to run the tests, check the hidden ones and save your progress. Sign up