Работа со строковыми данными

String и неизменяемость

String — неизменяемая последовательность кодовых единиц UTF-16. Любая операция, которая выглядит как изменение, возвращает новую строку:

String status = " new ";
String normalized = status.trim().toUpperCase(Locale.ROOT);
 
System.out.println(status);     // " new "
System.out.println(normalized); // "NEW"

Неизменяемость делает строки безопасными ключами хеш-таблиц, позволяет переиспользовать литералы и упрощает совместный доступ между потоками.

Пул строк

Литералы интернируются:

String first = "java";
String second = "java";
String third = new String("java");
 
first == second;       // true для этих литералов
first == third;        // false
first.equals(third);   // true

== сравнивает ссылки и не подходит для содержимого. equals сравнивает последовательность символов. intern() вручную возвращает каноническую строку из пула, но это редко нужно прикладному коду и не заменяет equals.

Конкатенация

Константные выражения компилятор может склеить заранее:

String header = "Java" + "Notes";

В цикле выражение result = result + part создаёт множество промежуточных строк. Для накопления используют StringBuilder:

StringBuilder builder = new StringBuilder();
for (String part : parts) {
    builder.append(part).append('\n');
}
String result = builder.toString();

StringBuilder не потокобезопасен. StringBuffer синхронизирован, но обычно совместное изменение буфера лучше вообще не разделять между потоками.

Для соединения готовой коллекции есть String.join и Collectors.joining.

Unicode

length() возвращает число char, а не обязательно число отображаемых символов. Символ вне Basic Multilingual Plane занимает surrogate pair:

String text = "A😀";
 
int units = text.length(); // 3
int points = text.codePointCount(0, text.length()); // 2

Даже code point не всегда равен видимой графеме: комбинируемые знаки могут отображаться вместе. Для обычной бизнес-валидации важно заранее определить, что именно значит «длина».

Сравнение и порядок

  • equals — точное сравнение содержимого.
  • equalsIgnoreCase — регистронезависимое сравнение с ограничениями Unicode/локали.
  • compareTo — лексикографический порядок по кодовым единицам.
  • Для пользовательской сортировки по языковым правилам нужен Collator, а не простой compareTo.

Nullable-строки можно сравнивать через Objects.equals.

Проверка пустоты

String empty = "";
String spaces = "   ";
 
empty.isEmpty();  // true
spaces.isBlank(); // true

isBlank учитывает пробельные символы, isEmpty — только нулевую длину. Оба метода требуют ненулевой ссылки.

Ловушки

  • Сравнивать строки через ==.
  • Забывать присвоить результат trim, replace, toLowerCase.
  • Склеивать строку оператором + внутри большого цикла.
  • Считать length() числом пользовательских символов.
  • Вызывать метод на nullable-строке.
  • Нормализовать идентификаторы через toLowerCase() без явной локали; для машинных токенов обычно используют Locale.ROOT.

Вопросы на собеседовании

  1. Почему String неизменяем?
  2. Чем == отличается от equals для строк?
  3. Что такое пул строк?
  4. Когда нужен StringBuilder?
  5. Почему String.length() не всегда равен числу символов на экране?
  6. Чем isEmpty отличается от isBlank?

Связанные заметки

Методы класса String · Класс Object · Equals и hashCode · Класс Collectors и его методы · Immutable классы и данные