1. Обзор
В этом руководстве мы узнаем, что делает объект неизменяемым, как добиться неизменности в Java и какие преимущества дает это.
2. Что такое неизменяемый объект?
Неизменяемый объект — это объект, внутреннее состояние которого остается постоянным после того, как он был полностью создан .
Это означает, что публичный API неизменяемого объекта гарантирует нам, что он будет вести себя одинаково в течение всей своей жизни.
Если мы посмотрим на класс String
, то увидим, что даже когда его API, кажется, предоставляет нам изменяемое поведение с помощью метода replace , исходная
String
не меняется:
String name = "foreach";
String newName = name.replace("dung", "----");
assertEquals("foreach", name);
assertEquals("bael----", newName);
API предоставляет нам методы только для чтения, он никогда не должен включать методы, изменяющие внутреннее состояние объекта.
3. Последнее
ключевое слово в Java
Прежде чем пытаться добиться неизменности в Java, мы должны поговорить о ключевом слове final .
В Java переменные по умолчанию изменяемы, то есть мы можем изменить значение, которое они содержат .
Используя ключевое слово final
при объявлении переменной, компилятор Java не позволит нам изменить значение этой переменной. Вместо этого он сообщит об ошибке времени компиляции:
final String name = "foreach";
name = "bael...";
Обратите внимание, что final
только запрещает нам изменять ссылку, которую содержит переменная, но не защищает нас от изменения внутреннего состояния объекта, на который она ссылается, с помощью своего общедоступного API:
final List<String> strings = new ArrayList<>();
assertEquals(0, strings.size());
strings.add("foreach");
assertEquals(0, strings.size());
Второй assertEquals
завершится ошибкой, потому что добавление элемента в список изменяет его размер, следовательно, это не неизменяемый объект.
4. Неизменяемость в Java
Теперь, когда мы знаем, как избежать изменения содержимого переменной, мы можем использовать его для создания API неизменяемых объектов.
Создание API неизменяемого объекта требует, чтобы мы гарантировали, что его внутреннее состояние не изменится независимо от того, как мы используем его API.
Шаг вперед в правильном направлении — использовать final
при объявлении его атрибутов:
class Money {
private final double amount;
private final Currency currency;
// ...
}
Обратите внимание, что Java гарантирует нам, что значение суммы
не изменится, это касается всех переменных примитивного типа.
Однако в нашем примере мы гарантируем только то, что валюта
не изменится, поэтому мы должны полагаться на API валюты
, чтобы защитить себя от изменений .
В большинстве случаев нам нужны атрибуты объекта для хранения пользовательских значений, а местом для инициализации внутреннего состояния неизменяемого объекта является его конструктор:
class Money {
// ...
public Money(double amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
public Currency getCurrency() {
return currency;
}
public double getAmount() {
return amount;
}
}
Как мы уже говорили ранее, чтобы соответствовать требованиям неизменяемого API, наш класс Money
имеет только методы только для чтения.
Используя API отражения, мы можем нарушить неизменяемость и изменить неизменяемые объекты . Однако отражение нарушает общедоступный API неизменяемого объекта, и обычно нам следует избегать этого.
5. Преимущества
Поскольку внутреннее состояние неизменяемого объекта остается постоянным во времени, мы можем безопасно делиться им между несколькими потоками .
Мы также можем использовать его свободно, и ни один из объектов, ссылающихся на него, не заметит никакой разницы, мы можем сказать, что неизменяемые объекты не имеют побочных эффектов .
6. Заключение
Неизменяемые объекты не меняют свое внутреннее состояние во времени, они потокобезопасны и не имеют побочных эффектов. Благодаря этим свойствам неизменяемые объекты также особенно полезны при работе с многопоточными средами.
Вы можете найти примеры, использованные в этой статье , на GitHub .