Перейти к основному содержимому

Строки в 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);

Готовые примеры:

Разбор:

  • фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
  • ключевые вызовы 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).


Производительность

  1. Не конкатенируйте String в цикле — StringBuilder или String.join.
  2. Не вызывайте + в hot path для сборки SQL/JSON — шаблонизаторы или StringBuilder.
  3. Интернирование (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 обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.

Связанные материалы