Card 06/ 08
Comparing Boxed Integers With == in Java
The comparison passes in every test with small numbers and fails in production with large ones.
In Java, == on two Integer objects compares whether they are the same object, not whether they hold the same number. Because Integer.valueOf shares objects from -128 to 127, the comparison happens to be true for small numbers and false for larger ones. This card shows the bug that results and the fix.
A stock check compares the quantities of two items, stored in a Map<String, Integer>. The map holds Integer objects, because Java collections cannot hold an int.
static boolean sameStock(Map<String, Integer> stock, String a, String b) {
return stock.get(a) == stock.get(b);
}Pens and pencils both have 120 in stock, and so do paper and card with 5,000 each.
What does sameStock return for each pair? Decide, then look.
import java.util.HashMap;
import java.util.Map;
public class FlBoxed {
static boolean sameStock(Map<String, Integer> stock, String a, String b) {
return stock.get(a) == stock.get(b);
}
public static void main(String[] args) {
Map<String, Integer> stock = new HashMap<>();
stock.put("pens", 120);
stock.put("pencils", 120);
stock.put("paper", 5000);
stock.put("card", 5000);
System.out.println("pens and pencils with ==: " + sameStock(stock, "pens", "pencils"));
System.out.println("paper and card with ==: " + sameStock(stock, "paper", "card"));
System.out.println("paper and card with equals: " + stock.get("paper").equals(stock.get("card")));
}
}pens and pencils with ==: true
paper and card with ==: false
paper and card with equals: trueThe pens and pencils compare as equal only by luck: 120 is in the cache, so both map entries hold the same shared object. 5,000 is outside it, so each put boxed a new object, and == compares two different objects that hold the same number. equals compares the numbers and is right for both.
The bug is costly because it hides. Test data tends to use small numbers, every test passes, and the first stock level over 127 in production gives the wrong answer. Compare boxed values with equals, or with Objects.equals where either may be null, or unbox them to int first. Static analysers such as SpotBugs report == between boxed numbers for this reason.
The same holds for every flyweight: a factory shares objects to save memory and promises nothing about identity. Code that depends on two objects being the same object, or being different ones, depends on a detail the factory is free to change.
← → move through sections and cards