Перейти к содержимому
Теория · Строки · урок 3 из 11

Строки неизменяемы

В прошлом уроке мы сразу печатали результат каждого метода. А что будет, если вызвать метод и никуда не положить его результат?

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

JAVA
public class Main {
    public static void main(String[] args) {
        String name = "anna";
        name.replace('a', 'o');
        System.out.println(name);  // anna
        String other = name.replace('a', 'o');
        System.out.println(other); // onno
        System.out.println(name);  // anna
    }
}

Давайте разберём программу по строкам:

  1. name.replace('a', 'o') создаёт новую строку onno, где каждая a заменена на o. Но её никуда не положили, и она пропадает.
  2. Первый println(name) печатает anna: строка name не изменилась.
  3. String other = name.replace('a', 'o'); снова создаёт строку onno и на этот раз кладёт её в other.
  4. println(other) печатает onno.
  5. Последний println(name) печатает anna: новая строка появилась, а старая осталась прежней.

Строка не меняется

Объект, который после создания нельзя изменить, называют неизменяемым. Строка в Java неизменяема: ни один её метод не трогает её символы. Методы, которые выглядят как изменение, возвращают новую строку:

  • toUpperCase() и toLowerCase();
  • replace(…);
  • strip();
  • substring(…).

Если результат такого метода не сохранить, он пропадёт. Компилятор об этом не предупреждает: вызвать метод и не взять его результат в Java можно.

Новое значение — в ту же переменную

Чтобы в переменной оказался изменённый текст, результат кладут в неё же: name = name.toUpperCase();. Строка при этом по-прежнему не меняется — меняется переменная. Посмотрим, что это значит:

JAVA
String city = "paris";
String before = city;
city = city.toUpperCase();
System.out.println(city);   // PARIS
System.out.println(before); // paris

Разберём по строкам:

  1. String city = "paris"; кладёт в city ссылку на строку paris. Переменная типа String, как и переменная-массив, хранит не сам объект, а ссылку на него: не карточку, а талон к ней.
  2. String before = city; копирует ссылку: обе переменные ведут к одной строке paris.
  3. city = city.toUpperCase(); создаёт новую строку PARIS и кладёт в city ссылку на неё.
  4. println(city) печатает PARIS.
  5. println(before) печатает paris: before ведёт к старой строке, а её никто не менял.

С массивом вышло бы иначе: запись через одну ссылку видна через другую. Со строкой так не бывает, потому что её символы не может поменять никто. Так же работает и +=: запись city += "!" значит city = city + "!" и тоже создаёт новую строку.

Здесь картинка с карточкой неточна. Ненужную карточку в жизни кто-то должен выбросить. В Java строку, к которой не ведёт ни одна ссылка, JVM убирает из памяти сама.

Зачем строки неизменяемы

Этот вопрос часто задают на собеседовании. Главный ответ: неизменяемую строку можно безопасно делить.

  • Одну строку могут хранить несколько переменных, и ни одна из них не испортит её для других.
  • Строку можно передать в метод: что бы он ни делал, ваша строка останется прежней.
  • Поэтому Java может хранить одинаковые строки в одном экземпляре и экономить память.

final — это другое

Слово final и неизменяемость строки часто путают, но запрещают они разное:

  • final запрещает переменной получить новое значение, то есть другую ссылку;
  • неизменяемость запрещает менять саму строку.
JAVA
final String greeting = "hi";
greeting = greeting.toUpperCase(); // ✗
TEXT
error: cannot assign a value to final variable greeting

Создать строку HI здесь можно, а положить её в final-переменную — нельзя.

Частая ошибка

Вызвали метод и забыли сохранить результат:

JAVA
String input = "  yes ";
input.strip(); // ✗
System.out.println(input.length()); // 6

Хотели убрать пробелы, а длина осталась 6. Метод strip() вернул новую строку yes, и она пропала. Сохраните результат: после input = input.strip(); вызов input.length() даёт 3.

Главное

  • Строка неизменяема: её методы не меняют символы, а возвращают новую строку.
  • Результат метода нужно сохранить, иначе он пропадёт: name = name.toUpperCase();.
  • Переменная типа String хранит ссылку; присваивание меняет ссылку, а не строку.
  • final запрещает менять переменную, а неизменяемость — саму строку.