In my last article, I gave a basic introduction to AutoValue, the code generating annotation processor that makes immutable value types in Java easy. Now I’d like to take a bit of a deeper look at AutoValue and how it works.
Compile Time Annotation Processing
First things first, AutoValue is a compile time annotation processor. This means that it only runs when you compile your code, as opposed to when your app is running. This has a few implications for your app, namely that including AutoValue doesn’t significantly effect your application’s performance or size.
The reason I say it won’t significantly effect your app is because it does add generated code to your final binary. This generally isn’t something you need to be worried about because, if you’re making value types, this is the code you should be generating anyway. As an added bonus, since AutoValue is tested, you can trust that there won’t be errors in the generated code and it will be performant.
If you’re not careful, however, it can be easy to accidentally include AutoValue, and all of it’s shaded dependencies, in your final binary. If, like me, you are an Android developer, this can have a serious impact on your app and users.
The correct way to include AutoValue so that it doesn’t bloat your app is to add it to the right configuration so that it only gets included in the annotation processing phase of your build process.
For Android, you’ll have to install the android-apt gradle plugin, then add AutoValue to the
apt configuration. You’ll also have to add it to the
provided configuration in order to get proper code completion.
1 2 3 4
To use AutoValue with Gradle in a
java project, you can install the apt plugin and add the dependencies to the
1 2 3 4
Maven users can simply add the dependency to the
1 2 3 4 5 6
The need to list the dependency in multiple configurations is because, unlike most annotations processors, AutoValue includes both the annotation and annotation processor in the same artifact. There is already an issue to fix this, and the fix will remove the need for listing the same dependency in multiple configurations.
Scanning Your classes
Once you have AutoValue added as a dependency, it will run when you compile your app to process all classes annotated with
@AutoValue. For a general overview of how annotation processors work in Java, check out Hannes Dorfmann’s great article, Annotation Processing 101.
@AutoValue annotated class, AutoValue first looks for all abstract methods to determine what it should generate. It does this by stepping through all abstract methods in your class, including those inherited by interfaces, which take no arguments and return a value. These methods are considered
properties, and AutoValue will generate appropriate fields and include them in the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Since AutoValue knows the difference between properties and your custom method, this means that you can have methods that return generated values, or perform other operations on your object.
Also, it’s important note that your property methods don’t need to be public. While they can’t be private as they have to be generated by the AutoValue subclass, they can be package private or protected to keep them hidden from your public API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Another thing to consider here is
toString. If you want to implement your own version of any of these methods, you can simply do that in your annotated class, and AutoValue will recognize that and won’t generate it’s own, meaning your implementation will be inherited and used in the final class.
1 2 3 4 5 6 7 8 9 10 11 12 13
Understanding the way AutoValue defines properties adds a lot of flexibility to your classes.
The Generated Code
As I mentioned in the previous article, AutoValue generates immutable value types by creating a constructor, member variables and their getters (properties), and implementing
toString, according to the rules outlined above. Let’s take a closer look at the generated class one piece at a time.
1 2 3 4 5 6 7
The first thing to notice about the generated class is that it subclasses the annotated abstract class. This means that in your consuming code, you can simply pass around
User objects, without anyone ever having to know that you use AutoValue to generate the implementation. This is intentional, and makes it easy to change the underlying implementation if AutoValue isn’t serving your needs.
Another small piece to consider is that the final generated class will always be the annotated class name prefixed with
AutoValue_. This is important in case you need to access generated code within your annotated class, like accessing the constructor in a static factory method.
Lastly, notice that the generated class is package private and final. This means that the AutoValue generated class isn’t visible outside your package, and shouldn’t ever need to be used by anything other than your annotated base class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
The generated constructor, again package private, takes as parameters all of the properties, in the order in which they were defined in the annotated class.
The arguments sent in are null checked within the constructor. All properties are considered non-nullable by default. If you want to make a property nullable, you can simply add an
@Nullable annotation to it.
1 2 3
The Member variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
There really isn’t anything special about the member variables and the associated method implementations, except that the fields are final, to enforce the immutability of the type.
equals(), hashCode() and toString()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
Lastly, we have
toString(), which are also quite default implementations.
As you can see, there is a lot that AutoValue is doing for us, but it is smart enough to infer what we want it to do and make reasonable decisions. This allows a lot of flexibility in the classes you write, as, for the most part, you can override what AutoValue would normally do by simply writing the code you would have anyway.
This, along with the fact that AutoValue itself is entirely hidden from end users, and the fact that it doesn’t add any unnecessary overhead to your project, make it a great fit for most apps.