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