1. Overview
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.
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.
2. Value Objects and Defensive Copies
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.
For example, consider a Person value object:
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:
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.
The Person class constructor may make a defensive copy of the favoriteMovies list using the List.copyOf static factory method:
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:
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.
3. AutoValue and Defensive Copies
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.
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.
Lastly, we add a static factory method to the Person class and invoke the generated AutoValue_Person constructor:
@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.
Therefore, we need to create a defensive copy of the favoriteMovies collection in the static factory method we defined:
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
When desired, we can use the @AutoValue.Builder annotation, which instructs AutoValue to generate a Builder class:
@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.
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.
@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:
public Person build() {
List<String> favoriteMovies = favoriteMovies();
List<String> copy = Collections.unmodifiableList(new ArrayList<>(favoriteMovies));
favoriteMovies(copy);
return autoBuild();
}
5. Conclusion
In this tutorial, we learned that AutoValue does not automatically create defensive copies, which is often of importance for Java Collections.
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.
As always, the code snippets used in this tutorial are available over on GitHub.