Строки спрашивают на каждом собесе по Java: почему String неизменяем, что такое пул, когда == даёт true и почему конкатенация в цикле плоха.
Неизменяемость
String объявлен final, его содержимое после создания не меняется. Все «изменяющие» методы (toUpperCase, replace, substring, concat) возвращают новую строку.
String s = "java";
s.toUpperCase(); // результат выброшен
System.out.println(s); // java
s = s.toUpperCase(); // теперь JAVA
Зачем так сделано:
- строку безопасно делить между потоками и кешировать;
- можно кешировать
hashCode(строки идеальные ключиHashMap); - работает пул строк;
- безопасность: путь к файлу или URL нельзя подменить после проверки.
С Java 9 строки хранят байты (byte[]) и флаг кодировки: латиница занимает 1 байт на символ (Compact Strings), остальное UTF-16.
Пул строк
Литералы ("java") и константные выражения кладутся в пул строк (он живёт в куче). Одинаковые литералы указывают на один объект. new String("java") всегда создаёт новый объект в куче. intern() возвращает каноничный экземпляр из пула.
String a = "java";
String b = "java";
String c = new String("java");
String d = "ja" + "va"; // константа, склеена компилятором
String part = "ja";
String e = part + "va"; // склейка в рантайме: новый объект
a == b // true
a == c // false
a == d // true
a == e // false
a == e.intern() // true
a.equals(c) // true
Если part объявить final String part = "ja", выражение станет константным и a == e даст true. Строки всегда сравнивают через equals (или equalsIgnoreCase), а == только как вопрос на собесе.
Конкатенация и StringBuilder
+ для строк с Java 9 компилируется в invokedynamic (StringConcatFactory), это быстро для одного выражения. Но в цикле каждая итерация создаёт новую строку и копирует всё накопленное: O(n²).
String res = "";
for (int i = 0; i < n; i++) res += i; // плохо
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) sb.append(i);
String res2 = sb.toString(); // хорошо
StringBuilder не потокобезопасен и быстр, StringBuffer синхронизирован и сегодня почти не нужен. Для склейки коллекции есть String.join(", ", list) и Collectors.joining.
Полезные методы
isBlank(),strip(),lines(),repeat(n)(Java 11);chars()отдаётIntStreamкодов символов;String.formatи"%d".formatted(x)(Java 15);- текстовые блоки
"""..."""для многострочного текста (Java 15).
length() считает UTF-16 единицы, а не символы: эмодзи занимает две. Для честного числа символов есть codePointCount.
Частые ошибки
- Сравнение через
==. - Вызов метода без присваивания результата:
s.trim();. - Конкатенация в цикле.
split("."): аргумент это регулярное выражение, точку надо экранироватьsplit("\\.").
Что спросят на собесе
- Почему String неизменяем и какие у этого плюсы?
- Что такое пул строк, где он находится и что делает
intern()? - Сколько объектов создаёт
new String("abc")? - Чем StringBuilder отличается от StringBuffer и от
+? - Что выведет сравнение строк через
==в разных вариантах?