Рубрики
Без рубрики

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

Узнайте, что нужно сделать, чтобы убедиться, что автоматическое значение создает защитные копии в конструкторах, которые оно записывает.

Автор оригинала: Johnathan Gilday.

1. Обзор

Создание объектов с неизменяемыми значениями вводит немного нежелательных шаблонов. Кроме того, стандартные типы коллекций Java потенциально могут вносить изменчивость в объекты значений, где эта черта нежелательна.

В этом уроке мы продемонстрируем, как создавать защитные копии коллекций при использовании автоматического значения , полезного инструмента для сокращения шаблонного кода для определения объектов неизменяемых значений.

2. Ценностные объекты и Защитные копии

Сообщество Java обычно рассматривает объекты значений как классификацию типов, представляющих неизменяемые записи данных. Конечно, такие типы могут содержать ссылки на стандартные типы коллекций Java, такие как java.util.Список .

Например, рассмотрим объект Person value:

class Person {
    private final String name;
    private final List favoriteMovies;

    // 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, List favoriteMovies) {
    this.name = name;
    this.favoriteMovies = List.copyOf(favoriteMovies);
}

Java 10 представила методы статической фабрики защитного копирования, такие как List.copy Of . Приложения, использующие более старые версии Java, могут создавать защитную копию с помощью конструктора копирования и одного из “неизменяемых” статических заводских методов в классе Collections :

public Person(String name, List favoriteMovies) {
    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, List favoriteMovies) {
        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, List 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 favoriteMovies();
}

4. Автооценка строителей и защитных копий

При желании мы можем использовать @AutoValue.Builder аннотация, которая указывает автоматическое значение для создания класса Builder :

@AutoValue
public abstract class Person {

    public abstract String name();
    public abstract List 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 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(List value);

    abstract List favoriteMovies();
    abstract Person autoBuild();

    public Person build() {
        // implementation omitted
    }
}

Наконец, мы предоставим пользовательскую реализацию метода build () , который создает защитную копию списка перед созданием Person . Мы будем использовать метод favoriteMovies() для получения Списка , заданного пользователем. Затем мы заменим список новой копией перед вызовом AutoBuild() для создания Person :

public Person build() {
    List favoriteMovies = favoriteMovies();
    List copy = Collections.unmodifiableList(new ArrayList<>(favoriteMovies));
    favoriteMovies(copy);
    return autoBuild();
}

5. Заключение

В этом уроке мы узнали, что автоматическое значение не создает автоматически защитные копии, что часто важно для коллекций Java.

Мы продемонстрировали, как создавать защитные копии в статических заводских методах, прежде чем создавать экземпляры автоматически генерируемых классов. Далее мы показали, как объединить пользовательский и сгенерированный код для создания защитных копий при использовании Autovalue Строитель классы.

Как всегда, фрагменты кода, используемые в этом руководстве, доступны на GitHub .