AutoValue: Generated Immutable Value Classes
The Google GitHub-hosted project AutoValue is interesting for multiple reasons. Not only does the project make it easy to write less Java code for “value objects,” but it also provides a conceptually simple demonstration of practical application of Java annotation processing. The auto/value project is provided by Google employees Kevin Bourrillion and Éamonn McManus and is licensed with an Apache Version 2 license.
The AutoValue User Guide is short and to the point and this conciseness and simplicity are reflective of the project itself. The User Guide provides simple examples of employing AutoValue, discusses why AutoValue is desirable, short answers to common questions in the How Do I… section, and outlines some best practices related to using AutoValue.
The following code listing contains a simple class I have hand-written called Person
. This class has been written with AutoValue in mind.
Person.java
package dustin.examples.autovalue; import com.google.auto.value.AutoValue; /** * Represents an individual as part of demonstration of * GitHub-hosted project google/auto/value * (see https://github.com/google/auto/tree/master/value). */ @AutoValue // concrete extension will be generated by AutoValue abstract class Person { /** * Create instance of Person. * * @param lastName Last name of person. * @param firstName First name of person. * @param birthYear Birth year of person. * @return Instance of Person. */ static Person create(String lastName, String firstName, long birthYear) { return new AutoValue_Person(lastName, firstName, birthYear); } /** * Provide Person's last name. * * @return Last name of person. */ abstract String lastName(); /** * Provide Person's first name. * * @return First name of person. */ abstract String firstName(); /** * Provide Person's birth year. * * @return Person's birth year. */ abstract long birthYear(); }
When using AutoValue to generate full-fledged “value classes,” one simply provides an abstract class (interfaces are intentionally not supported) for AutoValue to generate a corresponding concrete extension of. This abstract
class must be annotated with the @AutoValue
annotation, must provide a static
method that provides an instance of the value class, and must provide abstract
accessor methods of either public
or package scope that imply the value class’s supported fields.
In the code listing above, the static instance creation method instantiates a AutoValue_Person
object, but I have no such AutoValue_Person
class defined. This class is instead the name of the AutoValue generated class that will be generated when AutoValue’s annotation processing is executed against as part of the javac compiling of Person.java
. From this, we can see the naming convention of the AutoValue-generated classes: AutoValue_
is prepended to the source class’s name to form the generated class’s name.
When Person.java
is compiled with the AutoValue annotation processing applied as part of the compilation process, the generated class is written. In my case (using AutoValue 1.2 / auto-value-1.2.jar
), the following code was generated:
AutoValue_Person.java: Generated by AutoValue
package dustin.examples.autovalue; import javax.annotation.Generated; @Generated("com.google.auto.value.processor.AutoValueProcessor") final class AutoValue_Person extends Person { private final String lastName; private final String firstName; private final long birthYear; AutoValue_Person( String lastName, String firstName, long birthYear) { if (lastName == null) { throw new NullPointerException("Null lastName"); } this.lastName = lastName; if (firstName == null) { throw new NullPointerException("Null firstName"); } this.firstName = firstName; this.birthYear = birthYear; } @Override String lastName() { return lastName; } @Override String firstName() { return firstName; } @Override long birthYear() { return birthYear; } @Override public String toString() { return "Person{" + "lastName=" + lastName + ", " + "firstName=" + firstName + ", " + "birthYear=" + birthYear + "}"; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof Person) { Person that = (Person) o; return (this.lastName.equals(that.lastName())) && (this.firstName.equals(that.firstName())) && (this.birthYear == that.birthYear()); } return false; } @Override public int hashCode() { int h = 1; h *= 1000003; h ^= this.lastName.hashCode(); h *= 1000003; h ^= this.firstName.hashCode(); h *= 1000003; h ^= (this.birthYear >>> 32) ^ this.birthYear; return h; } }
Several observations can be made from examining the generated code:
- The generated class extends (implementation inheritance) the abstract class that was hand-written, allowing consuming code to use the hand-written class’s API without having to know that a generated class was being used.
- Fields were generated even though no fields were defined directly in the source class; AutoValue interpreted the fields from the provided
abstract
accessor methods. - The generated class does not provide “set”/mutator methods for the fields (get/accessor methods). This is an intentional design decision of AutoValue because a key concept of Value Objects is that they are immutable.
- Implementations of equals(Object), hashCode(), and toString() are automatically generated appropriately for each field with its type in mind.
- Javadoc comments on the source class and methods are not reproduced on the generated extension class.
One of the major advantages of using an approach such as AutoValue generation is that developers can focus on the easier higher level concepts of what a particular class should support and the code generation ensures that the lower-level details are implemented consistently and correctly. However, there are some things to keep in mind when using this approach and the Best Practices section of the document is a good place to read early to find out if AutoValue’s assumptions work for your own case.
- AutoValue is most likely to be helpful when the developers are disciplined enough to review and maintain the
abstract
“source” Java class instead of the generated class.- Changes to the generated classes would be overwritten the next time the annotation processing generated the class again or generation of that class would have to be halted so that this did not happen.
- The “source” abstract class has the documentation and other higher-level items most developers will want to focus on and the generated class simply implements the nitty gritty details.
- You’ll want to set your build/IDE up so that the generated classes are considered “source code” so that the
abstract
class will compile. - Special care must be taken when using mutable fields with AutoValue if one wants to maintain immutability (which is typically the case when choosing to use Value Objects).
- Review the Best Practices and How do I… sections to make sure no design assumptions of AutoValue make it not conducive to your needs.
Conclusion
AutoValue allows developers to write more concise code that focuses on high-level details and delegates the tedious implementation of low-level (and often error-prone) details to AutoValue for automatic code generation. This is similar to what an IDE’s source code generation can do, but AutoValue’s advantage over the IDE approach is that AutoValue can regenerate the source code every time the code is compiled, keeping the generated code current. This advantage of AutoValue is also a good example of the power of Java custom annotation processing.
Reference: | AutoValue: Generated Immutable Value Classes from our JCG partner Dustin Marx at the Inspired by Actual Events blog. |