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

Строки в Java

Разработчику Архитектору

Строки в Java

Строка в Java — объект класса java.lang.String. Литерал "текст" создаёт объект в пуле строк (string pool) для экономии памяти при повторном использовании одинаковых литералов.

См. также: типы данных, ввод-вывод и кодировки.


Неизменяемость (immutability)

После создания содержимое String не меняется. "Изменение" создаёт новый объект:

String s = "hello";
s = s + " world"; // новая строка, старая доступна сборщику мусора

Плюсы — потокобезопасность, безопасное хранение в 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();

С Java 11+ для простых случаев удобен String.join:

String csv = String.join(",", parts);

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


Сравнение строк

СпособНазначение
equalsСравнение содержимого
equalsIgnoreCaseБез учёта регистра (локаль не учитывается — осторожно с Unicode)
compareToЛексикографический порядок
==Сравнение ссылок, не содержимого
if ("admin".equals(role)) { } // безопасно при null в role
if (Objects.equals(a, b)) { } // оба могут быть null

Для сортировки по правилам языка — 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, содержимое одинаковое

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);

Спецификаторы — %s строка, %d целое, %f вещественное, %n перевод строки.

Text blocks (Java 15+) — многострочный текст без \n и конкатенации:

String json = """
{
"id": %d,
"name": "%s"
}
""".formatted(id, name);

В 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

Если вы обрабатываете пользовательский текст, чаще безопаснее работать с 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");

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;

Идея: нормализовать пользовательский ввод на границе системы, а в доменной логике работать с уже подготовленными значениями.


Регулярные выражения

Класс 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(";");

Для сложной валидации 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);
}

Это одновременно вопрос корректности, безопасности и поддержки кода.


Кодировки и байты

String в памяти — UTF-16 (пары char). При записи в файл или сеть нужна явная кодировка:

byte[] utf8 = text.getBytes(StandardCharsets.UTF_8);
String restored = new String(utf8, StandardCharsets.UTF_8);

На 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();

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