Что происходит при конкатенации строк через оператор +?
Когда вы используете + для склеивания строк, Java создаёт новую строку из двух (или более) исходных. Поскольку String иммутабельный, оригинальные строки не меняются.
🟢 Junior Level
Когда вы используете + для склеивания строк, Java создаёт новую строку из двух (или более) исходных. Поскольку String иммутабельный, оригинальные строки не меняются.
Пример:
String s1 = "Hello";
String s2 = "World";
String result = s1 + " " + s2; // "Hello World"
Важный момент: Если складывать строки в цикле через +, на каждой итерации создаётся новая строка. Это очень неэффективно.
// ПЛОХО — создаёт N новых объектов String
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // new String на каждой итерации!
}
// ХОРОШО — один объект StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
Правило: + удобен для простых выражений (1-2 строки), но запрещён в циклах.
Когда + вредит читаемости
Сложные выражения с вложенными вызовами методов:
"User: " + user.getName() + " (" + user.getAge() + " years)" — лучше StringBuilder или форматирование.
🟡 Middle Level
Как это работает
Поведение зависит от того, известны ли значения на этапе компиляции:
Константная конкатенация (Compile-time Constant Folding):
String s = "Hello" + " " + "World";
// Компилятор превращает это в:
String s = "Hello World"; // Одна строка в байт-коде, 0 операций в рантайме
Конкатенация переменных (Runtime):
String a = getName();
String b = getLastName();
String full = a + " " + b;
Что происходит “под капотом” зависит от версии Java (см. Senior секцию).
Типичные ошибки
-
Ошибка:
+в цикле Решение:StringBuilderвне цикла -
Ошибка:
split(".")— точка это regex-метасимвол Решение:split("\\.")илиsplit(Pattern.quote(".")) -
Ошибка:
null+ строка Решение: Результат будет"nullWorld". Это не ошибка, но может удивить.
Сравнение подходов
| Сценарий | Подход | Эффективность |
| ———————————- | ————————————- | ——————————— |
| "A" + "B" + "C" | + | Отлично (constant folding) |
| a + b + c (переменные, 1 строка) | + | Отлично (оптимизируется) |
| Цикл с += | StringBuilder | + — O(n²), StringBuilder — O(n) |
| Форматирование | String.format() или StringBuilder | Зависит от контекста |
🔴 Senior Level
Internal Implementation — Эволюция
Java 5-8: StringBuilder codegen
Компилятор заменял a + b + c на:
new StringBuilder().append(a).append(b).append(c).toString()
Проблема: байт-код “зашит” в .class. Новая стратегия оптимизации потребовала бы перекомпиляции.
JEP 280 (Java 9) ввёл invokedynamic для конкатенации. По умолчанию в Java 9 используется BC_SB, в Java 11+ стратегия может меняться через StringConcatFactory.
String full = a + " " + b;
Байт-код:
0: aload_1 // a
1: aload_2 // " "
2: aload_3 // b
3: invokedynamic #7 // makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
8: astore 4
invokedynamic ссылается на StringConcatFactory. При первом вызове bootstrap-метод генерирует оптимальный код для данной конкретной конкатенации.
Стратегии StringConcatFactory:
- MH_LF (MethodHandle with LambdaForm): Генерирует MethodHandle для конкатенации
- BC_SB (Bytecode StringBuilder): Генерирует байт-код с StringBuilder
- BH_SB (Bootstrap method with StringBuilder — compact): Оптимизированный вариант
Архитектурные Trade-offs
Плюсы invokedynamic:
- JVM выбирает стратегию в рантайме (adaptivity)
- Можно добавлять новые стратегии без перекомпиляции
- Меньше аллокаций — JVM знает все аргументы и выделяет
byte[]нужного размера сразу
Минусы:
- Bootstrap overhead при первом вызове (~1-5μs)
- Сложнее дебажить байт-код
Edge Cases
-
null конкатенация:
"val: " + null→"val: null".StringConcatFactoryиStringBuilderвызываютString.valueOf(null)→"null". -
Смешивание типов:
"Value: " + 42→StringConcatFactoryзнает типintи использует оптимальную конвертацию без создания промежуточногоString. -
Циклы:
invokedynamicоптимизирует только одно выражение. Цикл по-прежнему создаёт новый контекст конкатенации на каждой итерации:for (int i = 0; i < n; i++) s += i; // Каждая итерация: invokedynamic → новый byte[] → новый String // Total: O(n²) аллокаций
Производительность
| Сценарий | Java 8 (StringBuilder) | Java 9+ (invokedynamic) | | —————— | ———————- | ————————– | | 3 переменных | ~30ns | ~15ns | | 10 переменных | ~80ns | ~30ns | | 3 переменных + int | ~35ns | ~18ns | | Цикл 10K итераций | O(n²) | O(n²) (не оптимизируется!) |
Production Experience
Сценарий: Логирование log.info("User " + user.getId() + " action " + action) — 100K вызовов/сек:
- Java 8: 100K
new StringBuilder()→ ~30ms CPU - Java 9+: invokedynamic с прямой аллокацией → ~15ms CPU
- Если лог отфильтрован (level < INFO): оба варианта тратят CPU зря. Решение:
if (log.isInfoEnabled())
Best Practices для Highload
- Для простых конкатенаций (1 строка кода):
+— читаемо и эффективно - В циклах: всегда
StringBuilderсinitialCapacity - Для форматирования:
String.format()удобен, но медленнее (парсинг pattern). Альтернативы:MessageFormat, текстовые блоки (Java 15+), или ручный StringBuilder - Lazy evaluation: если конкатенация дорогая и может не понадобиться — отложите её
🎯 Шпаргалка для интервью
Обязательно знать:
- Оператор
+создаёт НОВУЮ строку — String иммутабельный, оригиналы не меняются - Constant folding:
"A" + "B" + "C"→"ABC"на этапе компиляции, 0 операций в рантайме - Java 5-8: компилятор заменяет
a + b + cнаnew StringBuilder().append(a).append(b).append(c).toString() - Java 9+:
invokedynamic+StringConcatFactory— JVM выбирает оптимальную стратегию в рантайме - В циклах
+создаёт O(n²) аллокаций — используйтеStringBuilder null + "text"→"nulltext"— не ошибка, но может удивить
Частые уточняющие вопросы:
- Почему
+в цикле — это плохо? — Каждая итерация создаёт новый StringBuilder + новый String. Для 1000 итераций: 1000 StringBuilder + 1000 String = O(n²). - Что такое
invokedynamicдля конкатенации? — В Java 9+ компилятор генерируетinvokedynamic makeConcatWithConstants. При первом вызове JVM выбирает оптимальную стратегию (MH_LF, BC_SB, BH_SB). - Оптимизирует ли компилятор циклы с
+? — Нет.invokedynamicоптимизирует только одно выражение, не цикл. - Что такое StringConcatFactory? — Фабрика в
java.lang.invoke, которая генерирует оптимальный код для конкретной конкатенации.
Красные флаги (НЕ говорить):
- ❌ “
+в цикле — нормально” — O(n²) аллокаций, используйтеStringBuilder - ❌ “Компилятор оптимизирует
+в цикле” — оптимизирует только одно выражение, не цикл - ❌ “
invokedynamicмедленнееStringBuilder” — для одного выражения — быстрее, для цикла — так же плохо - ❌ “Конкатенация через
+изменяет оригинальные строки” — String иммутабельный, всегда создаётся новая
Связанные темы:
- [[8. Как компилятор Java оптимизирует конкатенацию строк]]
- [[5. Когда использовать StringBuilder, а когда StringBuffer]]
- [[4. Почему String является иммутабельным]]