== или .equals(): частая ловушка
Классика собеседований: «Почему new String("a") == new String("a") — false, а Integer a = 100; Integer b = 100; a == b — true?» Если знаешь ответ — ты понимаешь, как Java хранит объекты в памяти.
Оператор
==для объектов в Java сравнивает не содержимое, а ссылки — то есть отвечает на вопрос «это один и тот же объект в памяти?», а не «одинаковые ли у них значения?».
Вопрос-крючок
String s1 = new String("a");
String s2 = new String("a");
System.out.println(s1 == s2); // ?
System.out.println(s1.equals(s2)); // ?
String s3 = "hello";
String s4 = "hello";
System.out.println(s3 == s4); // ?
Вывод:
false
true
trueПервое сравнение — false. Ключевое слово new явно говорит Java: «создай новый объект в куче», и так происходит дважды — получаются два разных объекта с одинаковым содержимым "a", но по разным адресам в памяти. == сравнивает именно адреса, поэтому получаем false, хотя строки выглядят одинаково.
Второе сравнение — true, потому что .equals() у String переопределён так, чтобы сравнивать посимвольно содержимое, а не адреса. Именно поэтому для сравнения содержимого объектов почти всегда нужен .equals(), а не ==.
Третье сравнение — снова true, и вот тут начинается интересное: s3 и s4 объявлены без new, просто через литералы "hello". И оказываются одним и тем же объектом.
Пул строк: почему литералы совпадают
Java хранит строковые литералы в специальной области памяти — пуле строк (String Pool). Когда компилятор встречает "hello", он сначала проверяет: а нет ли уже такой строки в пуле? Если есть — переиспользует существующий объект. Если нет — создаёт новый и кладёт его в пул. Поэтому s3 = "hello" и s4 = "hello" оба указывают на один и тот же объект в пуле — экономия памяти для одинаковых строк, которых в типичной программе очень много (например, названия полей, повторяющиеся сообщения).
А new String("a") нарочно обходит этот механизм: слово new заставляет Java создать новый объект в обычной куче, вне пула, даже если такая строка там уже есть. Отсюда и разница в поведении.
А что с числами? Кеш Integer от -128 до 127
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // ?
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // ?
Вывод:
true
falseЭто один из самых коварных вопросов на собеседовании, потому что результат зависит от конкретного числа. У класса Integer есть встроенный кеш: для значений от -128 до 127 Java заранее создаёт объекты Integer и переиспользует их при автоупаковке — примерно как со строковым пулом. Поэтому Integer a = 100 и Integer b = 100 — это один и тот же закешированный объект, и == даёт true.
А для 200 — числа вне диапазона кеша — каждый раз создаётся новый объект Integer, поэтому c == d — уже false, хотя код выглядит абсолютно так же, только число другое. Это именно та ловушка, из-за которой на реальных проектах случаются баги: код прекрасно работает и проходит тесты с маленькими числами, а потом ломается на продакшене на значении вроде 300.
Как сравнивать правильно
// Для объектов (String, Integer, свои классы) — всегда .equals()
if (s1.equals(s2)) { ... }
if (a.equals(b)) { ... }
// Для примитивов (int, double, boolean) — можно и нужно ==
int x = 5, y = 5;
if (x == y) { ... } // корректно, сравниваются значения
Правило простое: == сравнивает адреса для объектов и значения для примитивов; .equals() нужен всегда, когда важно содержимое объекта, а не то, один ли это экземпляр в памяти. Для String и Integer это правило особенно важно запомнить, потому что кеш и пул строк создают иллюзию, что == «обычно работает» — а потом код ломается на граничных значениях.
Частые ошибки на собеседовании
Сравнивают строки через == и удивляются, что иногда работает, а иногда нет — потому что зависит от того, литерал это или new String().
Не могут объяснить границы кеша Integer. Диапазон -128..127 выбран не случайно — это самые часто используемые в программах небольшие числа, и кеширование их экономит память без большого расхода ресурсов на сам кеш.
Путают равенство ссылок и равенство значений в общем — не только для строк и чисел, но и для своих собственных классов, где по умолчанию .equals() без переопределения ведёт себя так же, как == (об этом — в следующем уроке).
Итоги-шпаргалка
==для объектов сравнивает ссылки (адреса в памяти),.equals()— содержимое.- Строковые литералы (
"hello") берутся из пула строк и могут совпадать по ссылке;new String(...)всегда создаёт новый объект вне пула. Integerкеширует объекты для значений от -128 до 127 — внутри диапазона==может датьtrue, вне диапазона — почти всегдаfalse.- Для сравнения содержимого объектов используй
.equals(), не полагайся на==.