使用 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类中省略的访问器、构造函数、toString、equals和hashCode方法。
最后,我们向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();
}