Другое

Почему способ создания String влияет на сравнение в Java

Разница между строковыми литералами и new String() в Java. Как пул строк влияет на сравнение == и когда использовать equals(). Оптимизация памяти и производительности.

Почему способ создания String влияет на результат сравнения в Java?

Добрый день!

  1. Вернёт no:
java
String s1 = new String("Hello");
String s2 = new String("Hello");

if(s1 == s2)
    System.out.println("yes");
else
    System.out.println("no");
  1. Вернёт yes:
java
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 Pool) - это специальная область памяти в JVM, которая кэширует и переиспользует строковые литералы. Когда вы создаете строку с помощью литерального синтаксиса (например, "Hello"), JVM сначала проверяет, существует ли уже такая строка в пуле.

Согласно Java67, “когда вы создаете объект String с помощью оператора new(), он всегда создает новый объект в куче памяти. С другой стороны, если вы создаете объект с помощью синтаксиса строкового литерала, например "Java", он может вернуть существующий объект из пула строк”.

В современных версиях Java пул строк перемещен из области PermGen в основную кучу (heap), но его принцип работы остается аналогичным. Это оптимизация памяти, которая позволяет избежать создания множества идентичных строковых объектов.

Сравнение литералов строк и объектов String

Литералы строк (String Literals)

java
String s1 = "Hello";
String s2 = "Hello";

При таком подходе:

  1. JVM проверяет наличие строки "Hello" в пуле строк
  2. Если строка уже существует, s1 и s2 будут ссылаться на один и тот же объект
  3. Если строка не существует, она добавляется в пул, а переменные ссылаются на этот объект

Как объясняет Baeldung, “когда мы сравниваем строковый литерал с объектом String, созданным с помощью new(), оператор == вернет false”.

Объекты через конструктор (new String())

java
String s1 = new String("Hello");
String s2 = new String("Hello");

При таком подходе:

  1. Строка "Hello" сначала помещается в пул строк (как часть конструктора)
  2. Затем создается новый объект String в куче, копирующий значение из пула
  3. Переменные s1 и s2 ссылаются на разные объекты в куче

Как указано на net-informations.com, “в отличие от строковых литералов, строковые объекты, созданные с помощью ключевого слова new, не автоматически интернируются или хранятся в пуле строк”.

Поведение оператора == со строками

Оператор == в Java проверяет референциальное равенство - то есть, ссылаются ли обе переменные на один и тот же объект в памяти, а не равно ли их содержимое.

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”)

java
String s1 = new String("Hello");
String s2 = new String("Hello");

if(s1 == s2)
    System.out.println("yes");
else
    System.out.println("no"); // Выведется "no"

Процесс:

  1. new String("Hello") - строка "Hello" помещается в пул (если там ее нет)
  2. Создается новый объект String в куче со значением "Hello"
  3. s1 ссылается на этот объект в куче
  4. Повторяется то же самое для s2 - создается еще один объект в куче
  5. s1 и s2 ссылаются на разные объекты в памяти
  6. s1 == s2 возвращает false

Пример 2 (вернёт “yes”)

java
String s1 = "Hello";
String s2 = "Hello";

if(s1 == s2)
    System.out.println("yes"); // Выведется "yes"
else
    System.out.println("no");

Процесс:

  1. "Hello" - строка помещается в пул строк (если ее там нет)
  2. s1 ссылается на объект в пуле
  3. При создании s2 JVM видит, что "Hello" уже есть в пуле
  4. s2 также ссылается на тот же объект в пуле
  5. s1 и s2 ссылаются на один и тот же объект
  6. s1 == s2 возвращает true

Когда использовать intern() метод

Метод intern() позволяет явно добавить строку в пул строк и получить ссылку на нее:

java
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():

java
String s1 = new String("Hello");
String s2 = "Hello";
System.out.println(s1.equals(s2)); // true - содержимое одинаковое

Источники

  1. Difference between String literal and New String object in Java | Java67
  2. Guide to Java String Pool | Baeldung
  3. Difference between creating String as new() and literal | net-informations.com
  4. Java Strings: Understanding string literal and new operator | DEV Community
  5. Learn About Java String Pool And intern() Method | Coderolls

Заключение

  • Основное различие заключается в том, что литералы строк переиспользуются через пул, а new String() всегда создает новые объекты
  • Оператор == сравнивает ссылки на объекты, а не их содержимое
  • Для проверки равенства содержимого строк используйте метод equals()
  • Пул строк оптимизирует память, но имеет ограничения по размеру
  • Метод intern() позволяет явно добавлять строки в пул для сравнения через ==
Авторы
Проверено модерацией
Модерация