Defensive Copies for Collections Using AutoValue – 使用自动价值的集合的防御性副本

最后修改: 2019年 10月 30日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Creating immutable value objects introduces a bit of unwanted boilerplate. Also, Java’s standard collections types have the potential to introduce mutability to value objects where this trait is undesirable.

创建不可变的值对象引入了一些不必要的模板。此外,Java的标准集合类型有可能为值对象引入可变性,而这种特性是不可取的。

In this tutorial, we’ll demonstrate how to create defensive copies of collections when using AutoValue, a useful tool to reduce the boilerplate code for defining immutable value objects.

在本教程中,我们将演示在使用AutoValue时如何创建防御性的集合副本,这是一个有用的工具,可以减少定义不可变值对象的模板代码。

2. Value Objects and Defensive Copies

2.价值对象和防御性副本

The Java community generally considers value objects to be a classification of types that represent immutable data records. Of course, such types may contain references to standard Java collections types like java.util.List.

Java社区通常认为值对象是代表不可变数据记录的一种类型分类。当然,这种类型可能包含对标准Java集合类型的引用,比如java.util.List

For example, consider a Person value object:

例如,考虑一个Person值对象。

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

    // accessors, constructor, toString, equals, hashcode omitted
}

Because Java’s standard collection types may be mutable, the immutable Person type must protect itself from callers who would modify the favoriteMovies list after creating a new Person:

因为 Java 的标准集合类型可能是可变的,所以不可变的 Person 类型必须保护自己,防止调用者在创建一个新的 Person 之后修改 favoriteMovies 列表。

var favoriteMovies = new ArrayList<String>();
favoriteMovies.add("Clerks"); // fine
var person = new Person("Katy", favoriteMovies);
favoriteMovies.add("Dogma"); // oh, no!

The Person class must make a defensive copy of the favoriteMovies collection. By doing so, the Person class captures the state of the favoriteMovies list as it existed when the Person was created.

Person类必须为favoriteMovies集合制作一个防御性副本。通过这样做,Person类捕获了favoriteMovies列表的状态,因为它在创建Person时已经存在。

The Person class constructor may make a defensive copy of the favoriteMovies list using the List.copyOf static factory method:

Person类构造函数可以使用List.copyOf静态工厂方法对favoriteMovies列表进行防御性复制。

public Person(String name, List<String> favoriteMovies) {
    this.name = name;
    this.favoriteMovies = List.copyOf(favoriteMovies);
}

Java 10 introduced defensive copy static factory methods such as List.copyOf. Applications using older versions of Java may create a defensive copy using a copy constructor and one of the “unmodifiable” static factory methods on the Collections class:

Java 10引入了防御性拷贝静态工厂方法,如List.copyOf。使用旧版本 Java 的应用程序可以使用复制构造函数和 Collections 类上的 “不可修改 “静态工厂方法之一来创建防御性复制。

public Person(String name, List<String> favoriteMovies) {
    this.name = name;
    this.favoriteMovies = Collections.unmodifiableList(new ArrayList<>(favoriteMovies));
}

Note that there’s no need to make a defensive copy of the String name parameter since String instances are immutable.

注意,不需要对String name参数进行防御性复制,因为String实例是不可变的。

3. AutoValue and Defensive Copies

3.自动价值和防御性副本

AutoValue is an annotation processing tool for generating the boilerplate code for defining value object types. However, AutoValue does not make defensive copies when constructing a value object.

AutoValue是一个注释处理工具,用于生成定义值对象类型的模板代码。然而,AutoValue在构建价值对象时不会进行防御性复制。

The @AutoValue annotation instructs AutoValue to generate a class AutoValue_Person, which extends Person and includes the accessors, constructor, toString, equals, and hashCode methods we previously omitted from the Person class.

@AutoValue注解指示AutoValue生成一个AutoValue_Person类,它扩展了Person,并包括访问器、构造器、toStringequalshashCode方法,我们之前从Person类中省略了。

Lastly, we add a static factory method to the Person class and invoke the generated AutoValue_Person constructor:

最后,我们向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();
}

The constructor AutoValue generates will not automatically create any defensive copies, including one for the favoriteMovies collection.

构造函数AutoValue的生成将不会自动创建任何防御性的副本,包括为favoriteMovies集合创建一个。

Therefore, we need to create a defensive copy of the favoriteMovies collection in the static factory method we defined:

因此,我们需要在我们定义的静态工厂方法中创建一个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 Builders and Defensive Copies

4.自动价值构建者和防御性副本

When desired, we can use the @AutoValue.Builder annotation, which instructs AutoValue to generate a Builder class:

当需要时,我们可以使用@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();
    }
}

Because AutoValue generates the implementations of all the abstract methods, it’s not clear how to create a defensive copy of the List. We need to use a mixture of AutoValue-generated code and custom code to make defensive copies of collections just before the builder constructs the new Person instance.

因为AutoValue生成了所有抽象方法的实现,所以不清楚如何创建List的防御性副本。我们需要使用AutoValue生成的代码和自定义代码的混合物,在构建者构建新的Person实例之前,对集合进行防御性复制。

First, we’ll complement our builder with two new package-private abstract methods: favoriteMovies() and autoBuild(). These methods are package-private because we want to use them in our custom implementation of the build() method, but we don’t want consumers of this API to use them.

首先,我们将用两个新的打包私有的抽象方法来补充我们的构建器。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
    }
}

Finally, we’ll provide a custom implementation of the build() method that creates the defensive copy of the list before constructing the Person. We’ll use the favoriteMovies() method to retrieve the List that the user set. Next, we’ll replace the list with a new copy before calling autoBuild() to construct the Person:

最后,我们将提供一个build()方法的自定义实现,在构造Person之前创建列表的防御性副本。我们将使用favoriteMovies()方法来检索用户设置的List。接下来,在调用autoBuild()构建Person之前,我们将用一个新的副本替换该列表。

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

5. Conclusion

5.总结

In this tutorial, we learned that AutoValue does not automatically create defensive copies, which is often of importance for Java Collections.

在本教程中,我们了解到AutoValue不会自动创建防御性副本,这对Java集合来说往往是很重要的。

We demonstrated how to create defensive copies in static factory methods before constructing instances of AutoValue generated classes. Next, we showed how to combine custom and generated code to create defensive copies when using AutoValue’s Builder classes.

我们演示了在构建AutoValue生成类的实例之前,如何在静态工厂方法中创建防御性副本。接下来,我们展示了在使用AutoValue的Builder类时,如何结合自定义和生成的代码来创建防御性副本。

As always, the code snippets used in this tutorial are available over on GitHub.

一如既往,本教程中所使用的代码片段可在GitHub上获得