Перейти к основному содержимому

Защитные копии для коллекций с использованием AutoValue

· 4 мин. чтения

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 .