Кэш Integer и ==
Метод same сравнивает два Integer. Посмотрим, что он ответит для двух пар равных чисел:
public class Main {
static boolean same(Integer x, Integer y) {
return x == y;
}
public static void main(String[] args) {
System.out.println(same(100, 100)); // true
System.out.println(same(1000, 1000)); // false
}
}Метод один, числа в каждой паре равны, а ответы разные. Такой код проходит проверку на маленьких числах и ломается на больших, когда программой уже пользуются. Этот вопрос любят задавать на собеседовании, поэтому разберёмся до конца.
Вернёмся к монетам и конвертам. При вызове same(100, 100) автоупаковка кладёт каждое число в конверт — объект Integer. А == для объектов сравнивает ссылки, как у массивов и строк. Он спрашивает «это один и тот же конверт?», а не «одинаковые ли в конвертах монеты?».
Значит, оба числа 100 оказались в одном и том же конверте. Откуда он взялся?
Кэш Integer
Маленькие числа встречаются в программах постоянно: счётчики, индексы, номера месяцев. Делать для каждого новый конверт было бы расточительно. Поэтому класс Integer заранее готовит по объекту на каждое число от -128 до 127 и хранит их на одном подносе. Это кэш Integer.
Автоупаковка вызывает Integer.valueOf, а он сначала смотрит на поднос:
- для числа от
-128до127он отдаёт готовый объект из кэша, один и тот же при каждом вызове; - для любого другого числа он создаёт новый объект, каждый раз свой.
В виджете четыре строки: Integer a = 127;, Integer b = 127;, Integer c = 128; и Integer d = 128;. Поднос с кэшем виден с самого начала: он готов ещё до первой строки main. Выполняйте строки по одной и следите, откуда берётся объект для каждой переменной. Потом попробуйте угадать каждое сравнение и только затем нажмите на него.
1Integer a = 127;2Integer b = 127;3Integer c = 128;4Integer d = 128;
Кэш Integer готов заранее: в нём по объекту на каждое число от −128 до 127. Нажмите кнопку — она выполнит первую строку.
Разберём, что получилось:
a == bдаётtrue: оба разаvalueOf(127)отдал один и тот же объект с подноса.c == dдаётfalse: число128в кэш не входит, и каждыйvalueOf(128)создал свой объект.c.equals(d)даётtrue:equalsуIntegerсравнивает числа, а не ссылки.
Спецификация Java требует, чтобы числа от -128 до 127 всегда брались из кэша. По умолчанию кэш ими и ограничен, хотя его верхнюю границу можно поднять настройкой JVM.
Здесь картинка с подносом неточна. С настоящего подноса конверт забирают, и его там больше нет. А объект из кэша один на всю программу, и его делят все переменные. Это безопасно только потому, что число внутри Integer изменить нельзя.
Сравнивайте через equals
Починим метод same: пусть он сравнивает числа, а не ссылки.
public class Main {
static boolean same(Integer x, Integer y) {
return x.equals(y);
}
public static void main(String[] args) {
System.out.println(same(100, 100)); // true
System.out.println(same(1000, 1000)); // true
}
}Теперь обе пары дают true, и кэш на ответ больше не влияет. Что важно помнить про == с обёртками:
Integer == Integerсравнивает ссылки:trueбывает, только если это один объект, например из кэша;Integer == intраспаковывает обёртку и сравнивает числа: приInteger c = 128;выражениеc == 128даётtrue;equalsсравнивает числа всегда, в любом диапазоне.
На собеседовании вопрос звучит так: почему == для двух Integer со значением 127 даёт true, а со значением 128 — false? Хороший ответ состоит из трёх частей:
- автоупаковка вызывает
Integer.valueOf; valueOfотдаёт готовые объекты из кэша для чисел от-128до127и создаёт новые для остальных;==сравнивает ссылки, поэтому обёртки сравнивают черезequals.
Частая ошибка
Проверили через ==, исчерпан ли лимит:
Integer limit = 500;
Integer used = 500;
if (used == limit) { // ✗
System.out.println("limit reached");
}Программа ничего не печатает и не сообщает об ошибке. Число 500 не входит в кэш, поэтому used и limit ведут к двум разным объектам, и == даёт false. Пишите used.equals(limit) или храните такие числа в int.
Главное
- Автоупаковка вызывает
Integer.valueOf, а он для чисел от-128до127отдаёт объект из кэша — один и тот же при каждом вызове. - Для чисел вне кэша каждая упаковка создаёт новый объект.
==между двумяIntegerсравнивает ссылки, поэтому со значением127он даётtrue, а со значением128—false.- Числа в обёртках сравнивают через
equals;Integer == intсравнивает числа, потому что распаковывает обёртку.