String pool в Java: интернирование строк и сравнение через ==
Что такое String pool
String pool — это область памяти в куче (начиная с Java 7, до этого — в PermGen), где JVM хранит уникальные экземпляры строковых литералов. Идея простая: строки в Java иммутабельны, значит одинаковые литералы можно не создавать заново, а переиспользовать один объект на всех.
String a = "hello"; String b = "hello"; System.out.println(a == b); // true
Оба литерала попадают в пул при загрузке класса. Компилятор видит одинаковую строку и кладёт туда ссылку на один и тот же объект. Отсюда и совпадение по ==, хотя == сравнивает ссылки, а не содержимое.
Почему new String("hello") ломает ==
String a = "hello";
String b = new String("hello");
System.out.println(a == b); // false
System.out.println(a.equals(b)); // trueОператор new явно требует создать новый объект в куче, минуя пул. Получаем два разных объекта с одинаковым содержимым. Это классическая ловушка: код визуально одинаковый, а результат зависит от того, как строка была создана.
Конкатенация и compile-time constant
Важный нюанс: если строка составляется из литералов на этапе компиляции, javac сам всё склеит и положит в пул:
String a = "hel" + "lo"; // компилятор свернёт в "hello" String b = "hello"; System.out.println(a == b); // true
А если хотя бы одна часть — переменная, конкатенация происходит в runtime через StringBuilder, и результат в пул не попадает автоматически:
String x = "hel"; String a = x + "lo"; String b = "hello"; System.out.println(a == b); // false
intern() — ручной билет в пул
Метод intern() явно кладёт строку в пул (если её там ещё нет) и возвращает ссылку на канонический экземпляр:
String x = "hel"; String a = (x + "lo").intern(); String b = "hello"; System.out.println(a == b); // true
На практике intern() применяют редко, но не забыто: он полезен, когда в памяти крутятся тысячи одинаковых строк, созданных динамически (например, парсинг большого файла с повторяющимися токенами), и хочется дедупликации, чтобы не раздувать кучу. Но есть цена — пул сам занимает память и до Java 8 мог быть источником OutOfMemoryError в PermGen при неограниченном росте.
Как правильно сравнивать строки
Единственно верный способ сравнения содержимого — equals() (или Objects.equals(), если строка может быть null). == годится только тогда, когда вы осознанно сравниваете ссылки, например, чтобы проверить идентичность объекта в кэше.
String s1 = getFromDb();
String s2 = getFromCache();
if (s1.equals(s2)) { ... } // правильно
if (s1 == s2) { ... } // случайность, а не гарантияКак это спрашивают на собеседовании
Интервьюер обычно даёт кусок кода с new String() и литералами и просит предсказать вывод ==, а потом спрашивает — почему. Хороший ответ — не просто «потому что == сравнивает ссылки», а объяснение механики пула: где он живёт, когда строка туда попадает автоматически, а когда нужен intern().