Java: Неизменяемость строк
Подумаем, что выведет на экран следующий код:
var company = "hexlet";
company.toUpperCase(); // в верхний регистр
System.out.println(company); // => ?Кажется, что ответом будет "HEXLET", но это не так. Эта программа выведет "hexlet". Почему?
Дело в том, что строки в Java неизменяемы (или иммутабельны). После создания их содержимое нельзя изменить. Не существует методов, способных поменять саму строку. Любой метод строки только возвращает новую строку, а оригинал остается прежним.
Методы строк не меняют оригинал
Когда мы вызываем метод у строки, кажется, что мы меняем ее. Например, переводим в верхний регистр. На самом деле метод toUpperCase() возвращает новую строку в верхнем регистре, а исходная строка не меняется.
company = "hexlet"
company.toUpperCase() → "HEXLET" (новая строка)
company → "hexlet" (не изменилась)Чтобы не потерять результат, сохраним его в переменную:
var company = "hexlet";
var upper = company.toUpperCase();
System.out.println(upper); // => HEXLETЕсли не сохранить результат метода, он просто потеряется. Другие методы работают так же:
var text = " hi ";
var cleaned = text.trim();
System.out.println(cleaned); // => "hi", результат без пробелов
System.out.println(text); // => " hi ", строка не измениласьМетод trim() вернул новую строку без пробелов по краям, но сам text остался прежним.
Основная причина такого поведения — производительность. Строки и другие примитивные типы данных нельзя менять почти ни в одном современном языке. Неизменяемость позволяет Java переиспользовать одинаковые строки в памяти и экономить ресурсы. Она же упрощает многопоточный код, где одни и те же данные читают сразу несколько потоков.
Вторая причина связана с понятностью кода. Когда мы не изменяем данные, а создаем новые на основе старых, код проще анализировать и модифицировать. Особенно если с данными происходит много преобразований, с этим вам еще предстоит столкнуться. Случайно изменить значение строки невозможно, и это снимает целый класс ошибок.
Как менять данные
Но как же поступать, если данные нужно поменять? Для этого достаточно записать результат метода обратно в ту же переменную:
var language = "JAVA";
language = language.toLowerCase();
System.out.println(language); // => javaЭто уместно, когда суть данных не меняется. После toLowerCase() это тот же язык, просто в нижнем регистре.
С другой стороны, в такой ситуации можно создать новую переменную с другим именем:
var language = "JAVA";
var processedLanguage = language.toLowerCase();
System.out.println(processedLanguage); // => javaТакой подход нередко предпочтительнее по соображениям читаемости. Переменные, которые постоянно меняются, сложнее анализировать. Если результат метода представляет другую сущность, стоит дать ей отдельное имя. В итоге все зависит от задачи. С опытом придет понимание, какой подход лучше.
Задание
Данные, вводимые пользователями в формах, часто содержат лишние пробельные символы в конце или начале строки. Кроме того, пользователи могут вводить одно и то же в разном регистре, что потом мешает работе с данными. Поэтому перед тем как добавлять их, данные обрабатывают (говорят нормализуют). В базовую обработку входят два действия:
- Удаление концевых пробельных символов с помощью метода
.trim(), например, было:" hexlet\n ", стало:"hexlet" - Приведение к нижнему регистру с помощью метода
toLowerCase(). Было:"SUPPORT@hexlet.io", стало:"support@hexlet.io".
Обновите переменную email записав в неё то же самое значение, но обработанное по схеме указанной выше. Распечатайте то, что получилось, на экран.
Java: Неизменяемость строк
Подумаем, что выведет на экран следующий код:
var company = "hexlet";
company.toUpperCase(); // в верхний регистр
System.out.println(company); // => ?Кажется, что ответом будет "HEXLET", но это не так. Эта программа выведет "hexlet". Почему?
Дело в том, что строки в Java неизменяемы (или иммутабельны). После создания их содержимое нельзя изменить. Не существует методов, способных поменять саму строку. Любой метод строки только возвращает новую строку, а оригинал остается прежним.
Методы строк не меняют оригинал
Когда мы вызываем метод у строки, кажется, что мы меняем ее. Например, переводим в верхний регистр. На самом деле метод toUpperCase() возвращает новую строку в верхнем регистре, а исходная строка не меняется.
company = "hexlet"
company.toUpperCase() → "HEXLET" (новая строка)
company → "hexlet" (не изменилась)Чтобы не потерять результат, сохраним его в переменную:
var company = "hexlet";
var upper = company.toUpperCase();
System.out.println(upper); // => HEXLETЕсли не сохранить результат метода, он просто потеряется. Другие методы работают так же:
var text = " hi ";
var cleaned = text.trim();
System.out.println(cleaned); // => "hi", результат без пробелов
System.out.println(text); // => " hi ", строка не измениласьМетод trim() вернул новую строку без пробелов по краям, но сам text остался прежним.
Основная причина такого поведения — производительность. Строки и другие примитивные типы данных нельзя менять почти ни в одном современном языке. Неизменяемость позволяет Java переиспользовать одинаковые строки в памяти и экономить ресурсы. Она же упрощает многопоточный код, где одни и те же данные читают сразу несколько потоков.
Вторая причина связана с понятностью кода. Когда мы не изменяем данные, а создаем новые на основе старых, код проще анализировать и модифицировать. Особенно если с данными происходит много преобразований, с этим вам еще предстоит столкнуться. Случайно изменить значение строки невозможно, и это снимает целый класс ошибок.
Как менять данные
Но как же поступать, если данные нужно поменять? Для этого достаточно записать результат метода обратно в ту же переменную:
var language = "JAVA";
language = language.toLowerCase();
System.out.println(language); // => javaЭто уместно, когда суть данных не меняется. После toLowerCase() это тот же язык, просто в нижнем регистре.
С другой стороны, в такой ситуации можно создать новую переменную с другим именем:
var language = "JAVA";
var processedLanguage = language.toLowerCase();
System.out.println(processedLanguage); // => javaТакой подход нередко предпочтительнее по соображениям читаемости. Переменные, которые постоянно меняются, сложнее анализировать. Если результат метода представляет другую сущность, стоит дать ей отдельное имя. В итоге все зависит от задачи. С опытом придет понимание, какой подход лучше.
Задание
Данные, вводимые пользователями в формах, часто содержат лишние пробельные символы в конце или начале строки. Кроме того, пользователи могут вводить одно и то же в разном регистре, что потом мешает работе с данными. Поэтому перед тем как добавлять их, данные обрабатывают (говорят нормализуют). В базовую обработку входят два действия:
- Удаление концевых пробельных символов с помощью метода
.trim(), например, было:" hexlet\n ", стало:"hexlet" - Приведение к нижнему регистру с помощью метода
toLowerCase(). Было:"SUPPORT@hexlet.io", стало:"support@hexlet.io".
Обновите переменную email записав в неё то же самое значение, но обработанное по схеме указанной выше. Распечатайте то, что получилось, на экран.
Ваше упражнение проверяется по этим тестам
import static org.assertj.core.api.Assertions.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintStream;
class AppTest {
public static void main(String[] args) {
final var expected = "support@hexlet.io";
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
App.main(null);
String answer = out.toString();
if (answer != null && answer.endsWith("\n")) {
// remove linefeed added by println
answer = answer.substring(0, answer.length() - 1);
}
final var actual = answer;
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
System.out.println(actual);
assertThat(actual).isEqualTo(expected);
}
}Решение учителя откроется через:
20:00
