Contents

使用 AutoValue 的集合的防御性复制

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类型必须保护自己免受调用者在创建新Person后修改favoriteMovies列表的影响:

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

Person类必须对 favoriteMovies 集合进行防御性复制。通过这样做,Person类捕获了创建Person时的favoriteMovies列表的状态。

Person类的构造函数可以使用List.copyOf静态工厂方法制作favoriteMovies列表的防御性复制:

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并包括我们之前从Person类中省略的访问器、构造函数、toStringequalshashCode方法。

最后,我们向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 Builders 和防御性复制

如果需要,我们可以使用*@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的防御性复制。在构建器构造新的Person实例之前,我们需要混合使用 AutoValue 生成的代码和自定义代码来制作集合的防御性复制。

首先,我们将用两个新的包私有抽象方法来补充我们的构建器: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()方法来检索用户设置的List。接下来,在调用autoBuild()构造Person之前,我们将用新复制替换列表:

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