Автор оригинала: Johnathan Gilday.
1. Обзор
Создание объектов с неизменяемыми значениями вводит немного нежелательных шаблонов. Кроме того, стандартные типы коллекций Java потенциально могут вносить изменчивость в объекты значений, где эта черта нежелательна.
В этом уроке мы продемонстрируем, как создавать защитные копии коллекций при использовании автоматического значения , полезного инструмента для сокращения шаблонного кода для определения объектов неизменяемых значений.
2. Ценностные объекты и Защитные копии
Сообщество Java обычно рассматривает объекты значений как классификацию типов, представляющих неизменяемые записи данных. Конечно, такие типы могут содержать ссылки на стандартные типы коллекций Java, такие как java.util.Список .
Например, рассмотрим объект Person value:
class Person { private final String name; private final ListfavoriteMovies; // accessors, constructor, toString, equals, hashcode omitted }
Поскольку стандартные типы коллекций Java могут быть изменяемыми, неизменяемый тип Person должен защищать себя от абонентов, которые изменят список favoriteMovies после создания нового Person :
var favoriteMovies = new ArrayList(); favoriteMovies.add("Clerks"); // fine var person = new Person("Katy", favoriteMovies); favoriteMovies.add("Dogma"); // oh, no!
Класс Person должен сделать защитную копию коллекции любимых фильмов . Таким образом, класс Person фиксирует состояние списка избранных фильмов в том виде, в каком он существовал на момент создания Person .
Конструктор класса Person может создать защитную копию списка избранных фильмов с помощью метода List.copy static factory:
public Person(String name, ListfavoriteMovies) { this.name = name; this.favoriteMovies = List.copyOf(favoriteMovies); }
Java 10 представила методы статической фабрики защитного копирования, такие как List.copy Of . Приложения, использующие более старые версии Java, могут создавать защитную копию с помощью конструктора копирования и одного из “неизменяемых” статических заводских методов в классе Collections :
public Person(String name, ListfavoriteMovies) { this.name = name; this.favoriteMovies = Collections.unmodifiableList(new ArrayList<>(favoriteMovies)); }
Обратите внимание, что нет необходимости создавать защитную копию параметра String name , так как экземпляры String неизменяемы.
3. Автоматическое значение и Защитные копии
Автоматическое значение-это инструмент обработки аннотаций для создания шаблонного кода для определения типов объектов значений. Однако Auto Value не создает защитных копий при построении объекта value.
В аннотации @AutoValue указывается значение Auto для создания класса AutoValue_Person , который расширяет Person и включает методы доступа, конструктор, toString , equals и hashCode , которые мы ранее исключили из класса Person .
Наконец, мы добавляем статический фабричный метод в класс Person и вызываем сгенерированный конструктор AutoValue_Person :
@AutoValue public abstract class Person { public static Person of(String name, ListfavoriteMovies) { return new AutoValue_Person(name, favoriteMovies); } public abstract String name(); public abstract List favoriteMovies(); }
Конструктор AutoValue генерирует не будет автоматически создавать какие-либо защитные копии, в том числе для коллекции favoriteMovies .
Поэтому нам нужно создать защитную копию коллекции любимых фильмов в статическом фабричном методе , который мы определили:
public abstract class Person { public static Person of(String name, ListfavoriteMovies) { // create defensive copy before calling constructor var favoriteMoviesCopy = List.copyOf(favoriteMovies); return new AutoValue_Person(name, favoriteMoviesCopy); } public abstract String name(); public abstract List favoriteMovies(); }
4. Автооценка строителей и защитных копий
При желании мы можем использовать @AutoValue.Builder аннотация, которая указывает автоматическое значение для создания класса Builder :
@AutoValue public abstract class Person { public abstract String name(); public abstract ListfavoriteMovies(); public static Builder builder() { return new AutoValue_Person.Builder(); } @AutoValue.Builder public static class Builder { public abstract Builder name(String value); public abstract Builder favoriteMovies(List value); public abstract Person build(); } }
Поскольку значение Auto генерирует реализации всех абстрактных методов, неясно, как создать защитную копию списка /. Нам нужно использовать смесь кода, генерируемого автоматически, и пользовательского кода для создания защитных копий коллекций непосредственно перед тем, как конструктор создаст новый экземпляр Person .
Во-первых, мы дополним наш конструктор двумя новыми пакетами-частными абстрактными методами: любимые фильмы() и AutoBuild() . Эти методы являются закрытыми для пакетов, потому что мы хотим использовать их в нашей пользовательской реализации метода build () , но мы не хотим, чтобы потребители этого API использовали их.
@AutoValue.Builder public static abstract class Builder { public abstract Builder name(String value); public abstract Builder favoriteMovies(Listvalue); abstract List favoriteMovies(); abstract Person autoBuild(); public Person build() { // implementation omitted } }
Наконец, мы предоставим пользовательскую реализацию метода build () , который создает защитную копию списка перед созданием Person . Мы будем использовать метод favoriteMovies() для получения Списка , заданного пользователем. Затем мы заменим список новой копией перед вызовом AutoBuild() для создания Person :
public Person build() { ListfavoriteMovies = favoriteMovies(); List copy = Collections.unmodifiableList(new ArrayList<>(favoriteMovies)); favoriteMovies(copy); return autoBuild(); }
5. Заключение
В этом уроке мы узнали, что автоматическое значение не создает автоматически защитные копии, что часто важно для коллекций Java.
Мы продемонстрировали, как создавать защитные копии в статических заводских методах, прежде чем создавать экземпляры автоматически генерируемых классов. Далее мы показали, как объединить пользовательский и сгенерированный код для создания защитных копий при использовании Autovalue Строитель классы.
Как всегда, фрагменты кода, используемые в этом руководстве, доступны на GitHub .