ГлавнаяБлог › String pool в Java: интернирование строк и сравнение через ==

String pool в Java: интернирование строк и сравнение через ==

Что такое String pool

String pool — это область памяти в куче (начиная с Java 7, до этого — в PermGen), где JVM хранит уникальные экземпляры строковых литералов. Идея простая: строки в Java иммутабельны, значит одинаковые литералы можно не создавать заново, а переиспользовать один объект на всех.

String a = "hello";
String b = "hello";
System.out.println(a == b); // true

Оба литерала попадают в пул при загрузке класса. Компилятор видит одинаковую строку и кладёт туда ссылку на один и тот же объект. Отсюда и совпадение по ==, хотя == сравнивает ссылки, а не содержимое.

Пул строк — это как справочник контактов, где на одно и то же имя не завести две одинаковые карточки. Кто-то первый создал контакт «hello» — все остальные, кто ищет «hello», получают ссылку на ту же карточку, а не копию.

Почему 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 явно требует создать новый объект в куче, минуя пул. Получаем два разных объекта с одинаковым содержимым. Это классическая ловушка: код визуально одинаковый, а результат зависит от того, как строка была создана.

Сравнение строк через == — самая частая причина багов вида «работает на моей машине». Код может годами жить без проблем, потому что все строки приходят из литералов или из пула, а потом ловит NPE-подобный баг, когда строка приходит из БД, сети или конкатенации через StringBuilder.

Конкатенация и 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 при неограниченном росте.

intern() — это как принести самодельную открытку в тот самый справочник контактов и сказать: «раз такая карточка уже есть — дайте мне ссылку на неё, а свою копию я выброшу».

Как правильно сравнивать строки

Единственно верный способ сравнения содержимого — equals() (или Objects.equals(), если строка может быть null). == годится только тогда, когда вы осознанно сравниваете ссылки, например, чтобы проверить идентичность объекта в кэше.

String s1 = getFromDb();
String s2 = getFromCache();
if (s1.equals(s2)) { ... } // правильно
if (s1 == s2) { ... } // случайность, а не гарантия

Как это спрашивают на собеседовании

Интервьюер обычно даёт кусок кода с new String() и литералами и просит предсказать вывод ==, а потом спрашивает — почему. Хороший ответ — не просто «потому что == сравнивает ссылки», а объяснение механики пула: где он живёт, когда строка туда попадает автоматически, а когда нужен intern().

Почему new String("abc") == "abc" даёт false?
new явно создаёт новый объект в куче, минуя String pool, поэтому ссылка отличается от ссылки на литерал из пула, хотя содержимое одинаковое.
Где физически хранится String pool в современной JVM?
Начиная с Java 7 — в куче (heap), а не в PermGen/Metaspace, как было раньше. Это снизило риск OutOfMemoryError от переполнения пула.
Есть ли смысл вызывать intern() на каждой строке для оптимизации памяти?
Нет, это оправдано только при явном дублировании множества одинаковых строк; в общем случае intern() добавляет накладные расходы на поиск в пуле и может навредить производительности.

Ещё разборы