Строки в Java
Строки в Java
★ Строка в Java — объект класса java.lang.String. Литерал "текст" создаёт объект в пуле строк (string pool) для экономии памяти при повторном использовании одинаковых литералов.
См. также: типы данных, ввод-вывод и кодировки.
Неизменяемость (immutability)
После создания содержимое String не меняется. "Изменение" создаёт новый объект:
String s = "hello";
s = s + " world"; // новая строка, старая доступна сборщику мусора
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Плюсы — потокобезопасность, безопасное хранение в HashMap как ключ, предсказуемое поведение при передаче в методы.
Минус: конкатенация в цикле даёт O(n²) по копированию — используйте StringBuilder.
String, StringBuilder, StringBuffer
| Класс | Изменяемый | Потокобезопасный | Когда использовать |
|---|---|---|---|
String | нет | да (как объект) | Константы, ключи, возврат из API |
StringBuilder | да | нет | Сборка текста в одном потоке |
StringBuffer | да | да (устаревший подход) | Legacy-код; в новом коде — StringBuilder |
StringBuilder sb = new StringBuilder();
for (String part : parts) {
if (!sb.isEmpty()) sb.append(',');
sb.append(part);
}
String result = sb.toString();
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
StringBuilder,isEmpty,append,toStringзадают основную механику этого примера: обработку, проверку и формирование результата. StringBuilderснижает число промежуточных объектов: строка накапливается в изменяемом буфере и превращается вStringтолько в конце.- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
С Java 11+ для простых случаев удобен String.join:
String csv = String.join(",", parts);
Готовые примеры:
- разворот строки;
- палиндром;
- подсчёт слов — Lab — консольные задачи на Java.
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
joinзадают основную механику этого примера: обработку, проверку и формирование результата. - чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Сравнение строк
| Способ | Назначение |
|---|---|
equals | Сравнение содержимого |
equalsIgnoreCase | Без учёта регистра (локаль не учитывается — осторожно с Unicode) |
compareTo | Лексикографический порядок |
== | Сравнение ссылок, не содержимого |
if ("admin".equals(role)) { } // безопасно при null в role
if (Objects.equals(a, b)) { } // оба могут быть null
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
equalsзадают основную механику этого примера: обработку, проверку и формирование результата. - чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Для сортировки по правилам языка — Collator с локалью.
String pool и intern
Литералы вроде "OK" попадают в общий пул строк, поэтому JVM переиспользует один объект для одинаковых литералов.
String a = "java";
String b = "java";
System.out.println(a == b); // true, один объект из пула
String c = new String("java");
System.out.println(a == c); // false, другой объект
System.out.println(a.equals(c)); // true, содержимое одинаковое
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
println,String,equalsзадают основную механику этого примера: обработку, проверку и формирование результата. - здесь важно различие
==иequals— первый сравнивает ссылки, второй — содержимое, что критично для строк и value-объектов. - после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
intern() возвращает каноническую строку из пула. Используйте его точечно и только после измерений, потому что это trade-off между памятью и стоимостью операций.
Форматирование
String.format и formatted (Java 15+):
String msg = String.format("User %s, balance %.2f", name, balance);
String msg2 = "User %s, balance %.2f".formatted(name, balance);
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
format,formattedзадают основную механику этого примера: обработку, проверку и формирование результата. - чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Спецификаторы — %s строка, %d целое, %f вещественное, %n перевод строки.
Text blocks (Java 15+) — многострочный текст без \n и конкатенации:
String json = """
{
"id": %d,
"name": "%s"
}
""".formatted(id, name);
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
formattedзадают основную механику этого примера: обработку, проверку и формирование результата. - чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
В text block завершающие пробелы в строках по умолчанию удаляются; отступ выравнивается по самой левой непустой строке.
Unicode и символы за пределами BMP
char в Java хранит 16-битную единицу UTF-16, а некоторые символы (эмодзи, редкие иероглифы) занимают две единицы.
String s = "A😀B";
System.out.println(s.length()); // 4, а не 3
System.out.println(s.codePointCount(0, s.length())); // 3
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
println,length,codePointCountзадают основную механику этого примера: обработку, проверку и формирование результата. - чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Если вы обрабатываете пользовательский текст, чаще безопаснее работать с code points, а не с индексами char.
Полезные методы String
" hello ".trim(); // пробелы по краям (Unicode — см. strip)
" hello ".strip();
"a,b,c".split(",");
"path/file.txt".substring(5);
"index.html".endsWith(".html");
"ERROR: msg".startsWith("ERROR");
"abc".repeat(3); // "abcabcabc"
"hello".contains("ell");
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
trim,strip,split,substring,endsWith,startsWith,repeatзадают основную механику этого примера: обработку, проверку и формирование результата. - чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
isBlank() (Java 11) — пустая или только пробельные символы.
Частые задачи со строками в backend
String normalized = input.strip().replace("\u00A0", " "); // неразрывный пробел
boolean isUuid = normalized.matches("^[0-9a-fA-F\\-]{36}$");
String safeLog = normalized.length() > 200 ? normalized.substring(0, 200) + "..." : normalized;
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
strip,replace,matches,length,substringзадают основную механику этого примера: обработку, проверку и формирование результата. - чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
- после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Идея: нормализовать пользовательский ввод на границе системы, а в доменной логике работать с уже подготовленными значениями.
Регулярные выражения
Класс java.util.regex:
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("order-42-item");
if (m.find()) {
String digits = m.group();
}
boolean ok = "user@example.com".matches(".+@.+\\..+");
String[] parts = "a;b;c".split(";");
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
compile,matcher,find,group,matches,splitзадают основную механику этого примера: обработку, проверку и формирование результата. - связка
PatternиMatcherразделяет этапы компиляции regex и поиска совпадений, что удобнее для повторного использования шаблона. - после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Для сложной валидации email/телефонов предпочтительны специализированные библиотеки или Bean Validation в рекомендациях, а не разросшийся regex в коде.
Строки в JSON и SQL
- Для JSON старайтесь использовать сериализаторы (
Jackson,Gson), а не ручную конкатенацию. - Для SQL всегда применяйте
PreparedStatement, не склеивайте запросы строками.
String sql = "select * from users where email = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, email);
}
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
prepareStatement,setStringзадают основную механику этого примера: обработку, проверку и формирование результата. - параметризация через
PreparedStatementотделяет SQL-шаблон от пользовательских данных и закрывает типичный риск SQL-инъекции. - после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
Это одновременно вопрос корректности, безопасности и поддержки кода.
Кодировки и байты
String в памяти — UTF-16 (пары char). При записи в файл или сеть нужна явная кодировка:
byte[] utf8 = text.getBytes(StandardCharsets.UTF_8);
String restored = new String(utf8, StandardCharsets.UTF_8);
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
getBytes,Stringзадают основную механику этого примера: обработку, проверку и формирование результата. - явное указание
UTF-8на encode/decode делает поведение стабильным между разными ОС и окружениями. - после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.
На Windows пути и консоль часто дают сбои при смешении системной кодировки и UTF-8 — подробнее в ввод-вывод и конфигурациях JVM (file.encoding, native.encoding).
Производительность
- Не конкатенируйте
Stringв цикле —StringBuilderилиString.join. - Не вызывайте
+в hot path для сборки SQL/JSON — шаблонизаторы илиStringBuilder. - Интернирование (
intern) — только при доказанной необходимости; в modern JVM пул управляется иначе, чем в старых учебниках.
Мини-чек-лист
- Сравнение строк идет через
equals/Objects.equals, не через==. - Кодировка явно указана на границе I/O (ввод-вывод и файлы).
- Длинные текстовые шаблоны вынесены в text blocks.
- Конкатенации в циклах заменены на
StringBuilderилиjoin.
Строки и коллекции
Преобразование потока строк — Stream API:
List<String> upper = lines.stream()
.map(String::strip)
.filter(s -> !s.isEmpty())
.map(String::toUpperCase)
.toList();
Разбор:
- фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
- ключевые вызовы
stream,map,filter,isEmpty,toListзадают основную механику этого примера: обработку, проверку и формирование результата. - в stream-цепочке операции применяются последовательно к каждому элементу, а терминальный вызов (
collect/toList) материализует итоговую коллекцию. - после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
- при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.