В чём разница между созданием String через литерал и через new?
Создать строку в Java можно двумя способами, и они работают по-разному:
🟢 Junior Level
Создать строку в Java можно двумя способами, и они работают по-разному:
Через литерал (рекомендуется):
String s = "Hello";
JVM проверяет String Pool. Если такая строка уже есть — возвращает ссылку на неё. Если нет — создаёт новую в пуле.
Через конструктор new (не рекомендуется):
String s = new String("Hello");
Всегда создаёт новый объект в обычной куче, игнорируя String Pool для самого объекта.
Пример:
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
System.out.println(s1 == s2); // true (один объект в пуле)
System.out.println(s1 == s3); // false (s3 — отдельный объект)
System.out.println(s1.equals(s3)); // true (содержимое одинаковое)
Когда использовать: В 99% случаев используйте литералы. Конструктор new String(...) нужен в основном при конвертации из byte[]/char[], а также в редких случаях для создания гарантированно независимой копии (defensive copying).
🟡 Middle Level
Как это работает внутри
Литерал:
String s = "Hello";
- Байт-код: инструкция
LDC(Load Constant) - JVM проверяет String Pool по хешу строки
- Если найдена — возвращает ссылку, если нет — создаёт объект в пуле
- Оптимизация происходит на этапе загрузки класса
Конструктор:
String s = new String("Hello");
- Байт-код:
NEW→DUP→LDC "Hello"→INVOKESPECIAL - Литерал
"Hello"уже находится в String Pool new String(...)создаёт второй объект в Regular Heap с тем же содержимым- Результат: два объекта в памяти с одинаковым текстом
Типичные ошибки
-
Ошибка:
new String("literal")для “гарантии уникальности” Решение: Это антипаттерн. Уникальность объектов строк почти никогда не нужна -
Ошибка: Думать, что
new String()быстрее Решение: Литералы быстрее — они используют пул и не создают лишних объектов
Когда конструктор всё же нужен
| Случай | Пример | Объяснение |
| ———————– | ————————– | ————————– |
| Из byte[] | new String(bytes, UTF_8) | Основной способ при I/O |
| Из char[] | new String(chars) | Конвертация символов |
| Из StringBuffer/Builder | builder.toString() | Неявный вызов конструктора |
🔴 Senior Level
Internal Implementation
Байт-код литерала:
0: ldc #2 // String Hello
2: astore_1
Одна инструкция — загрузка константы из Constant Pool класса.
Байт-код конструктора:
0: new #3 // class java/lang/String // NEW — выделяет память для нового объекта String
3: dup // DUP — дублирует ссылку для конструктора
4: ldc #2 // String Hello // LDC — загружает литерал из Constant Pool
6: invokespecial #4 // Method ..."<init>":... // INVOKESPECIAL — вызывает конструктор String
9: astore_1
Четыре инструкции: выделение памяти, дублирование ссылки, загрузка константы, вызов конструктора.
Архитектурные Trade-offs
Почему конструктор существует:
- Исторически: для создания строк из сырых данных (byte[], char[], int[] кодовых точек)
- Практически:
new String(String original)копирует содержимое в новый массив (в Java 7+), гарантируя независимость от оригинала
Зачем может понадобиться независимая копия:
- До Java 7u6:
substring()разделялchar[]родителя.new String(substring)копировал данные, освобождая родительский массив для GC - В современных Java это неактуально —
substring()всегда копирует данные
Edge Cases
- String с intern():
String s = new String("Hello").intern();Создаёт объект в Heap, затем
intern()проверяет пул. Если"Hello"уже в пуле — возвращает ссылку из пула, а объект из Heap становится мусором. - Constant Folding:
String s = "Hel" + "lo"; // Компилятор схлопывает в "Hello" на этапе компиляцииРезультат будет в String Pool, как если бы написали
"Hello". - Runtime конкатенация:
String a = "Hel"; String s = a + "lo"; // НЕ в пуле! Создаётся через invokedynamic/StringBuilder
Производительность
| Операция | Аллокации | Время | GC pressure |
| ——————— | ————————- | ——————– | ————- |
| "Hello" | 0 (если в пуле) | ~0 | None |
| new String("Hello") | 1 объект + литерал в пуле | ~10-20ns | Medium |
| "Hello".intern() | 0 | Lookup в хеш-таблице | Low |
Production Experience
В парсерах JSON/XML, где одно и то же поле ("name", "id", "type") встречается миллионы раз:
- Без пула: миллионы объектов String → значительный GC overhead
- С пулом: десятки уникальных строк → экономия 90%+ памяти на строках
Monitoring
# Посмотреть количество объектов String
jmap -histo:live <pid> | grep java.lang.String
# Анализ через JOL
System.out.println(GraphLayout.parseInstance(s1).toFootprint());
System.out.println(GraphLayout.parseInstance(s2).toFootprint());
Best Practices
- Никогда не используйте
new String("literal")без обоснованной причины - Для перевода строки из Heap в Pool используйте
.intern() - Если нужна гарантированно независимая копия —
new String(existingString)(хотя это редко требуется)
🎯 Шпаргалка для интервью
Обязательно знать:
- Литерал
String s = "Hello"— JVM проверяет String Pool, возвращает ссылку из пула или создаёт в пуле new String("Hello")— всегда создаёт НОВЫЙ объект в куче (даже если литерал уже в пуле)- Литерал использует байт-код
LDC, конструктор —NEW+DUP+LDC+INVOKESPECIAL - Constant folding:
"Hel" + "lo"компилятор схлопывает в"Hello"на этапе компиляции - Runtime конкатенация (
a + "lo") — НЕ в пуле, создаётся черезinvokedynamic/StringBuilder new String("...")создаёт 2 объекта: литерал в пуле + объект в Heap
Частые уточняющие вопросы:
- Сколько объектов создаёт
new String("Hello")? — Один или два. Литерал"Hello"уже в пуле (при загрузке класса),new String()создаёт второй объект в Heap. - Зачем вообще существует конструктор
new String(String)? — Для создания независимой копии (defensive copying). До Java 7u6 это было нужно для освобождения памяти от shared array. - Что быстрее: литерал или
new String()? — Литерал быстрее — 0 аллокаций (если уже в пуле).new String()— ~10-20ns + GC pressure. - Когда
new String()из byte[]/char[] нужен? — Это основной способ при I/O:new String(bytes, UTF_8),new String(chars).
Красные флаги (НЕ говорить):
- ❌ “
new String("literal")создаёт уникальную строку” — создаёт дубликат, а не уникальность - ❌ “Литерал и
new String()— одно и то же” — разное размещение в памяти - ❌ “
new String()быстрее литерала” — наоборот, литерал быстрее - ❌ “Constant folding работает для переменных” — только для
static finalконстант и литералов
Связанные темы:
- [[1. Как работает String Pool]]
- [[3. Когда стоит использовать intern()]]
- [[9. Можно ли использовать == для сравнения String]]
- [[7. Что происходит при конкатенации строк через оператор +]]