
Привет! Давайте поговорим кое о чем, что может удивить вас в Java — почему иногда сравнение чисел с помощью == работает не так, как вы ожидаете. Да, мы говорим о том, почему 1 == 1 возвращает true, а 128 == 128 — false. Звучит странно, правда? Давайте разберемся!
Любопытство: почему 1 == 1 возвращает true, а 128 == 128 — false
Итак, вы могли бы подумать, что при сравнении двух чисел в Java, например
1 == 1 или 128 == 128, результат всегда будет true, потому что, ну, у нас же одно и то же число с обеих сторон равенства, так? Оказывается, в Java не все так просто.Вот небольшой кусочек кода, который это демонстрирует:
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false
Integer x = 1;
Integer y = 1;
System.out.println(x == y); // true
Так, давайте объясню, что здесь происходит. Это не просто магия. Такие дела происходят из-за кэширования целых чисел в Java. Давайте разберемся детальнее.
Магия кэширования целых чисел
В Java класс Integer определенным образом оптимизирован. Это называется кэшированием целых чисел. Java кэширует объекты Integer для значений в диапазоне от -128 до 127. Зачем? Ну, Java пытается оптимизировать использование памяти, а значения в этом диапазоне используются довольно часто, поэтому вместо создания новых объектов для каждого числа она переиспользует одни и те же.
Вы делаете что-то типа:
Integer x = 1;
Integer y = 1;
А Java не создает два отдельных объекта в памяти для
x и y. Вместо этого она переиспользует кэшированный объект Integer для 1. Вот почему уравнение x == y возвращает true. И x, и y обращаются к одному и тому же объекту в памяти.Но вы можете сделать по-другому:
Integer a = 128;
Integer b = 128;
Поскольку 128 находится за пределами кэшируемого диапазона, Java создает два отдельных объекта Integer. Следовательно, хотя и
a, и b имеют значение 128, это два разных объекта в памяти. Вот почему a == b возвращает false — это сравнение двух разных мест в памяти, а не самих значений.
Разрушим представления: == vs .equals()
Теперь мы готовы поговорить о важном различии. В Java оператор
== сравнивает ссылки, то есть проверяет, указывают ли две переменные на один и тот же объект в памяти. С другой стороны, метод .equals() сравнивает значения внутри этих объектов.Давайте чуть изменим код выше:
Integer a = 128;
Integer b = 128;
System.out.println(a.equals(b)); // true
Видите разницу?
a.equals(b) возвращает true, потому что сравнивает значение внутри двух объектов Integer, которое равно 128. Его не волнует тот факт, что a и b принадлежат разным объектам.Диапазон от -128 до 127
Java кэширует значения объекта Integer в диапазоне от -128 до 127. Вы можете воспринимать этот диапазон как золотую середину Java для экономии памяти. Получается, для любого целого числа в этом диапазоне Java повторно использует один и тот же объект. Для значений за пределами этого диапазона, например, 128 или 1000, новые объекты создаются каждый раз.
Вы даже можете изменить этот диапазон, задав аргумент Java Virtual Machine:
-XX:AutoBoxCacheMax=size
Но по умолчанию он зафиксирован на 127.
Пример
Мы используем
System.identityHashCode() для отображения хэш-кодов. Эти хэш-коды все еще могут различаться, когда объекты разные (например, c = 128 и d = 128), а могут совпадать, когда обращаются к одному и тому же кэшированному объекту (например, e = 1 и f = 1).Integer c = 128;
Integer d = 128;
System.out.println(System.identityHashCode(c)); // HashCode of c
System.out.println(System.identityHashCode(d)); // HashCode of d
Integer e = 1;
Integer f = 1;
System.out.println(System.identityHashCode(e)); // HashCode of e (кэшированный объект)
System.out.println(System.identityHashCode(f)); // HashCode of f (тот же кэшированный объект)
Вывод может быть примерно таким:
212628335
2111991224
false
292938459
292938459
true
Для значений от -128 до 127 вы увидите один и тот же хэш-код. Но для значений из-за его пределов (например, 128) Java выделяет разные участки памяти.
Почему это вообще имеет значение
Вы можете получить неожиданные результаты, когда сравниваете числа с помощью
== вместо .equals() в вашем коде, особенно в случаях со значениями за пределами кэшируемого диапазона. Так какой вывод мы можем сделать? Используйте .equals() для сравнения значений объектов, если только вам не нужно специально сравнивать участки памяти (что бывает редко в большинстве приложений).Кэширование целых чисел в Java — это одна из тех удобных оптимизаций, которая отлично работает в большинстве случаев. Но как только вы выходите за пределы диапазона от -128 до 127, все может стать сложнее, если вы полагаетесь на==для сравнения чисел. Просто помните, что для сравнения значений нужно использовать.equals(), и все будет в порядке!
