В рамках некоторых интервью по программированию, которые я недавно проводил, иногда возникает проблема инвариантности. Я сам не слишком догматичен, но всякий раз, когда изменяемое состояние не требуется, я стараюсь избавиться от кода, вызывающего изменчивость, которая обычно наиболее заметна в структурах данных. Однако, похоже, существует некоторое непонимание концепции неизменности, разработчики обычно считают, что наличиеfinal
цитировать, илиval
В Kotlin или Scala достаточно сделать объекты неизменяемыми. Этот пост в блоге углубляется вНеизменяемые ссылки и неизменяемые структуры данных.
Преимущества неизменяемых структур данных
Неизменяемые структуры данных имеют значительные преимущества, такие как:
- нет недопустимого состояния
- потокобезопасность
- простой для понимания код
- Легче тестировать код
- Доступно для типов значений
нет недопустимого состояния
Когда объект неизменяем, трудно оставить объект в недопустимом состоянии. Объект может быть создан только через его конструктор, который обеспечивает допустимость объекта. Таким образом, параметры, необходимые для действительного состояния, могут быть принудительно применены. один пример:
Address address = new Address();
address.setCity("Sydney");
// address is in invalid state now, since the country hasn’t been set.
Address address = new Address("Sydney", "Australia");
// Address is valid and doesn’t have setters, so the address object is always valid.
потокобезопасность
Поскольку объект не может быть изменен, поэтому его можно поделиться между потоками, но не будет конкурентоспособной или проблемой мутации данных.
простой для понимания код
Как и в примере кода для недопустимого состояния, часто проще использовать конструктор, чем метод инициализации. Это связано с тем, что конструкторы применяют обязательные параметры, тогда как методы установки или инициализатора не применяют их во время компиляции.
Легче тестировать код
Поскольку объекты более предсказуемы, нет необходимости тестировать все перестановки методов инициализации, т. е. когда вызывается конструктор класса, объект действителен или недействителен. Другие части кода, использующие эти классы, становятся более предсказуемыми, с меньшимNullPointerException
Шанс. Иногда при передаче объекта некоторые методы могут изменить состояние объекта. Например:
public boolean isOverseas(Address address) {
if(address.getCountry().equals("Australia") == false) {
address.setOverseas(true); // address has now been mutated!
return true;
} else {
return false;
}
}
В общем, приведенный выше код является плохой практикой. Он возвращает логическое значение и может изменить состояние объекта. Это затрудняет понимание и тестирование кода. Лучшее решение — начать сAddress
Класс удаляется, и по названию тестовой страны возвращается логическое значение. Лучший способ - перенести эту логику вAddress
сам класс(address.isOverseas()
). Когда вам нужно установить состояние, сделайте копию исходного объекта без изменения ввода.
Доступно для типов значений
Представьте себе сумму, скажем, 10 долларов. 10 долларов всегда будут 10 долларов. В коде, это может выглядетьpublic Money(final BigInteger amount, final Currency currency)
. Как вы можете видеть в этом коде, невозможно изменить значение 10 долларов на что-либо другое, поэтому приведенное выше безопасно для типов значений.
Окончательные ссылки не делают объекты неизменяемыми
Как уже упоминалось, одна из проблем, с которыми я часто сталкиваюсь, заключается в том, что большой процент этих разработчиков не полностью понимает разницу между конечными ссылками и неизменяемыми объектами. Кажется, что общее понимание среди этих разработчиков состоит в том, что в тот момент, когда переменная становится окончательной, структура данных становится неизменной. К сожалению, это не так просто, и я хотел бы раз и навсегда развеять это заблуждение:
A final reference does not make your objects immutable!
Другими словами, приведенный ниже код ненетСделать объект неизменным:
final Person person = new Person("John");
почему нет?好吧,虽然person
Это последнее поле и не может быть переназначено, ноPerson
У класса может быть метод установки или другой метод мутатора, который может делать что-то вроде этого:
person.setName("Cindy");
Это очень легко сделать независимо от конечного модификатора. или,Person
Класс может предоставить такой список адресов. Доступ к этому списку позволяет добавлять в него адреса, поэтому измените их следующим образом.person
Объект:
person.getAddresses().add(new Address("Sydney"));
Хорошо, теперь, когда мы разобрались с этим, давайте немного углубимся в то, как мы можем сделать классы неизменяемыми. При разработке нашего класса мы должны помнить о нескольких вещах:
- Не раскрывайте внутреннее состояние переменным образом
- Не изменять статус внутри
- Убедитесь, что подклассы не переопределяют вышеуказанное поведение.
В соответствии со следующими рекомендациями, давайте разработаем лучшийPerson
классная версия.
public final class Person {// final class, can’t be overridden by subclasses
private final String name; // final for safe publication in multithreaded applications
private final List<Address> addresses;
public Person(String name, List<Address> addresses) {
this.name = name;
this.addresses = List.copyOf(addresses); // makes a copy of the list to protect from outside mutations (Java 10+).
// Otherwise, use Collections.unmodifiableList(new ArrayList<>(addresses));
}
public String getName() {
return this.name; // String is immutable, okay to expose
}
public List<Address> getAddresses() {
return addresses; // Address list is immutable
}
}
public final class Address { // final class, can’t be overridden by subclasses
private final String city; // only immutable classes
private final String country;
public Address(String city, String country) {
this.city = city;
this.country = country;
}
public String getCity() {
return city;
}
public String getCountry() {
return country;
}
}
Теперь можно использовать следующий код:
import java.util.List;
final Person person = new Person("John", List.of(new Address(“Sydney”, "Australia"));
Теперь приведенный выше код неизменяем, но из-заPerson
иAddress
Дизайн класса, а также наличие конечной ссылки, поэтому переменная person не может быть переназначена чему-либо еще.
Обновление: какнекоторые людиКак уже упоминалось, приведенный выше код по-прежнему изменчив, потому что я не копирую список адресов в конструкторе. Следовательно, если неArrayList()
Вызывая new в конструкторе, вы по-прежнему можете сделать следующее:
final List<Address> addresses = new ArrayList<>();
addresses.add(new Address("Sydney", "Australia"));
final Person person = new Person("John", addressList);
addresses.clear();
Однако, поскольку в конструкторе создается новая копия, приведенный выше код не повлияет на список адресов класса, скопированные ссылкиPerson
, что делает код безопасным.
Я надеюсь, что вышеизложенное поможет понять разницу между final и immutable. Если у вас есть какие-либо комментарии или отзывы, пожалуйста, дайте мне знать в комментариях ниже.
Английский оригинал:D zone.com/articles/IM…
Общедоступный номер: Galaxy № 1
Контактный адрес электронной почты: public@space-explore.com
(Пожалуйста, не перепечатывайте без разрешения)