Почему способ создания String влияет на сравнение в Java
Разница между строковыми литералами и new String() в Java. Как пул строк влияет на сравнение == и когда использовать equals(). Оптимизация памяти и производительности.
Почему способ создания String влияет на результат сравнения в Java?
Добрый день!
- Вернёт
no:
String s1 = new String("Hello");
String s2 = new String("Hello");
if(s1 == s2)
System.out.println("yes");
else
System.out.println("no");
- Вернёт
yes:
String s1 = "Hello";
String s2 = "Hello";
if(s1 == s2)
System.out.println("yes");
else
System.out.println("no");
Почему эти два примера дают разные результаты при сравнении строк с помощью оператора ==?
В Java способ создания строки определяет, будут ли переменные ссылаться на один и тот же объект в памяти, что напрямую влияет на результат сравнения с помощью оператора ==. При использовании литералов строк ("Hello") JVM помещает их в специальный пул строк и возвращает существующий объект, тогда как при использовании конструктора new String("Hello") всегда создается новый объект в куче, даже если идентичная строка уже существует.
Содержание
- Что такое пул строк в Java?
- Сравнение литералов строк и объектов String
- Поведение оператора == со строками
- Практические примеры и разбор
- Когда использовать intern() метод
- Оптимизация памяти и производительность
Что такое пул строк в Java?
Пул строк (String Pool) - это специальная область памяти в JVM, которая кэширует и переиспользует строковые литералы. Когда вы создаете строку с помощью литерального синтаксиса (например, "Hello"), JVM сначала проверяет, существует ли уже такая строка в пуле.
Согласно Java67, “когда вы создаете объект String с помощью оператора
new(), он всегда создает новый объект в куче памяти. С другой стороны, если вы создаете объект с помощью синтаксиса строкового литерала, например"Java", он может вернуть существующий объект из пула строк”.
В современных версиях Java пул строк перемещен из области PermGen в основную кучу (heap), но его принцип работы остается аналогичным. Это оптимизация памяти, которая позволяет избежать создания множества идентичных строковых объектов.
Сравнение литералов строк и объектов String
Литералы строк (String Literals)
String s1 = "Hello";
String s2 = "Hello";
При таком подходе:
- JVM проверяет наличие строки
"Hello"в пуле строк - Если строка уже существует,
s1иs2будут ссылаться на один и тот же объект - Если строка не существует, она добавляется в пул, а переменные ссылаются на этот объект
Как объясняет Baeldung, “когда мы сравниваем строковый литерал с объектом String, созданным с помощью new(), оператор == вернет false”.
Объекты через конструктор (new String())
String s1 = new String("Hello");
String s2 = new String("Hello");
При таком подходе:
- Строка
"Hello"сначала помещается в пул строк (как часть конструктора) - Затем создается новый объект
Stringв куче, копирующий значение из пула - Переменные
s1иs2ссылаются на разные объекты в куче
Как указано на net-informations.com, “в отличие от строковых литералов, строковые объекты, созданные с помощью ключевого слова
new, не автоматически интернируются или хранятся в пуле строк”.
Поведение оператора == со строками
Оператор == в Java проверяет референциальное равенство - то есть, ссылаются ли обе переменные на один и тот же объект в памяти, а не равно ли их содержимое.
// Пример 1: оба литерала - один и тот же объект
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true - оба ссылаются на один объект в пуле
// Пример 2: объекты через конструктор - разные объекты
String s3 = new String("Hello");
String s4 = new String("Hello");
System.out.println(s3 == s4); // false - разные объекты в куче
// Пример 3: литерал vs конструктор
String s5 = "Hello";
String s6 = new String("Hello");
System.out.println(s5 == s6); // false - пула и кучи
Как объясняет DEV Community, “если вы создаете строку с помощью оператора new, он не будет использовать строковый пул, а создаст другой строковый объект в куче”.
Практические примеры и разбор
Давайте детально разберем оба примера из вопроса:
Пример 1 (вернёт “no”)
String s1 = new String("Hello");
String s2 = new String("Hello");
if(s1 == s2)
System.out.println("yes");
else
System.out.println("no"); // Выведется "no"
Процесс:
new String("Hello")- строка"Hello"помещается в пул (если там ее нет)- Создается новый объект
Stringв куче со значением"Hello" s1ссылается на этот объект в куче- Повторяется то же самое для
s2- создается еще один объект в куче s1иs2ссылаются на разные объекты в памятиs1 == s2возвращаетfalse
Пример 2 (вернёт “yes”)
String s1 = "Hello";
String s2 = "Hello";
if(s1 == s2)
System.out.println("yes"); // Выведется "yes"
else
System.out.println("no");
Процесс:
"Hello"- строка помещается в пул строк (если ее там нет)s1ссылается на объект в пуле- При создании
s2JVM видит, что"Hello"уже есть в пуле s2также ссылается на тот же объект в пулеs1иs2ссылаются на один и тот же объектs1 == s2возвращаетtrue
Когда использовать intern() метод
Метод intern() позволяет явно добавить строку в пул строк и получить ссылку на нее:
String s1 = new String("Hello");
String s2 = s1.intern(); // s2 теперь ссылается на объект из пула
String s3 = "Hello";
System.out.println(s2 == s3); // true - оба ссылаются на объект из пула
Как отмечает Baeldung, “ручная интернация строки сохранит ее ссылку в пуле, и JVM вернет эту ссылку при необходимости”.
Метод intern() может быть полезен для:
- Сравнения строк с помощью
==вместоequals() - Оптимизации памяти при работе с большим количеством повторяющихся строк
- Производительности в условиях, где требуется частое сравнение строк
Оптимизация памяти и производительность
Преимущества литералов строк:
- Экономия памяти - один объект используется многократно
- Более быстрое сравнение с помощью
== - Автоматическая оптимизация со стороны JVM
Недостатки литералов строк:
- Строки в пуле никогда не собираются мусором
- Пул имеет фиксированный размер и может переполниться
- Все строки в пуле доступны всему приложению
Преимущества new String():
- Гарантированно создается новый объект
- Можно контролировать время жизни объекта
- Подходит для чувствительных к безопасности данных
Недостатки new String():
- Большее потребление памяти
- Медленнее сравнение (требуется вызов
equals()) - Неоптимально для повторяющихся строк
Важно: Для сравнения содержимого строк, а не ссылок, всегда используйте метод
equals():javaString s1 = new String("Hello"); String s2 = "Hello"; System.out.println(s1.equals(s2)); // true - содержимое одинаковое
Источники
- Difference between String literal and New String object in Java | Java67
- Guide to Java String Pool | Baeldung
- Difference between creating String as new() and literal | net-informations.com
- Java Strings: Understanding string literal and new operator | DEV Community
- Learn About Java String Pool And intern() Method | Coderolls
Заключение
- Основное различие заключается в том, что литералы строк переиспользуются через пул, а
new String()всегда создает новые объекты - Оператор
==сравнивает ссылки на объекты, а не их содержимое - Для проверки равенства содержимого строк используйте метод
equals() - Пул строк оптимизирует память, но имеет ограничения по размеру
- Метод
intern()позволяет явно добавлять строки в пул для сравнения через==