В чому різниця між створенням 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. Що відбувається при конкатенації рядків через оператор +]]