An Introduction to AutoValue
Value types in Java are hard. Well, not hard, but tedious. Google’s AutoValue library makes them much easier and has just received the long awaited update that adds the flexibility of extensions.
Value Types in Java⌗
Before we can talk about how great AutoValue is, let’s look at the problem it solves: value types.
A value type is simply an immutable objects whose equality is based on property values, as opposed to identity. Think of a Money
object:
public class Money {
public Currency currency;
public long amount;
}
This is a simple value type with two properties, amount
and currency
. If we have two money objects with the same currency and amount, they are considered equal, regardless of object references.
While this seems perfectly straightforward, Java doesn’t make it quite this simple. Value types should be immutable, but our current Money object doesn’t enforce that. So we need to update it like so:
public final class Money {
private Currency currency;
private long amount;
public Money(Currency currency, long amount) {
this.currency = currency;
this.amount = amount;
}
public Currency currency() {
return currency;
}
public long amount() {
return amount;
}
}
As you can see, we have to make the class final so that it can’t be subclassed (has to do with equality guarantees), make our fields private, use getters to retrieve the values, and add a constructor so that users can create Money objects. This turned our simple little 4 line class into 14 lines.
But we’re not done yet. Equality is still by reference. This means that $2 != $2
. We need to implement equals()
, and also hashCode()
if we ever want to use this object in a set, or as keys in a map.
public final class Money {
private Currency currency;
private long amount;
public Money(Currency currency, long amount) {
this.currency = currency;
this.amount = amount;
}
public Currency currency() {
return currency;
}
public long amount() {
return amount;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
if (Double.compare(money.amount, amount) != 0) return false;
return currency.equals(money.currency);
}
@Override public int hashCode() {
int result;
long temp;
result = currency.hashCode();
result = 31 * result + (int) (amount ^ (amount >>> 32));
return result;
}
}
To implement hashCode()
and equals()
I’ve simply asked IntelliJ to create them for me, which is great as long as I don’t change fields. This has also turned my not-so-little 14 line class into a full grown 29 line class.
But what happens if we want to log this object? Surely we’d like something a little better than Money@12CE469
to show up in our logs. Therefore we need to add a toString()
method.
public final class Money {
private Currency currency;
private long amount;
public Money(Currency currency, long amount) {
this.currency = currency;
this.amount = amount;
}
public Currency currency() {
return currency;
}
public long amount() {
return amount;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
if (money.amount != amount) return false;
return currency.equals(money.currency);
}
@Override public int hashCode() {
int result;
long temp;
result = currency.hashCode();
result = 31 * result + (int) (amount ^ (amount >>> 32));
return result;
}
@Override public String toString() {
return "Money{" +
"currency=" + currency +
", amount=" + amount +
'}';
}
}
And now we’ve gone from 4 lines to 34 lines in this class. If you believe in the relationship between lines of code and potential for bugs, then you see the problem here, to say nothing of the extra work required to create and manage this thing.
AutoValue to the Rescue⌗
This is where AutoValue comes in. It’s an annotation processor that generates all of the mundane value-type code for you, so you can focus on more important things.
To add AutoValue to your project, you simply need to add it’s single dependency to your annotation processing classpath. Being in the annotation processing classpath means that no dependencies of AutoValue get added to your final artifact, only the generated code.
dependencies {
apt 'com.google.auto.value:auto-value:1.2-rc1'
}
To use AutoValue to turn our Money object into a full fledged value type, you simply need to add an annotation to the now abstract class.
@AutoValue public abstract class Money {
public abstract Currency currency();
public abstract long amount();
}
Behind the scenes, AutoValue generates all of the private fields, the constructor, hashCode()
, equals()
, and toString()
for you. This generated class, named by simply prefixing your class name with AutoValue_
, is package private, so all you or your users ever really deal with is the annotated Money class.
Since the subclass’s constructor is package private, you also need to add static factory methods, Item 1 in Josh Bloch’s Effective Java, to create your objects.
@AutoValue public abstract class Money {
public abstract Currency currency();
public abstract long amount();
public static Money create(Currency currency, long amount) {
return new AutoValue_Money(currency, amount);
}
}
With that we’re done. This value class is effectively identical to the previous example, but is contained in 7 lines of code, and only contains what we need.
One feature that shows the benefit of AutoValue’s method of subclassing is that you can add any additional code to the class you want. So, for instance, if you have derived fields, they can be added to the Money class without the need for helper classes.
@AutoValue public abstract class Money {
public abstract Currency currency();
public abstract long amount();
public static Money create(Currency currency, long amount) {
return new AutoValue_Money(currency, amount);
}
public String displayString() {
return currency().symbol() + amount();
}
}
Testing: The Hidden Benefit⌗
One benefit of generated code is that it doesn’t need to be tested. Whereas in the earlier example all of the code would need to be tested to guard against regressions and ensure proper functionality, with AutoValue, since the generator itself has been tested and is known to produce correct code, we don’t have to worry about testing all of the boilerplate code.
Extensions⌗
So now that you can easily create value types in Java, what if you want to use your generated value types with other systems, like JSON serializers, or Android’s Parcel class? That’s where extensions come in.
With the release of AutoValue 1.2-rc1, we finally have support for AutoValue Extensions. With Extensions you can have additional functionality, in most cases simply by adding a dependency to your annotation processor classpath.
For example, say you wanted your Money object above to be Parcelable. By simply adding the AutoValue: Parcel Extension to your annotation processing classpath and making your class implement Parcelable, the generated code will be Parcelable.
dependencies {
provided 'com.google.auto.value:auto-value:1.2-rc1' // needed for Android Studio
apt 'com.google.auto.value:auto-value:1.2-rc1'
apt 'com.ryanharter.auto.value:auto-value-parcel:0.2.0'
}
@AutoValue public abstract class Money implements Parcelable {
public abstract Currency currency();
public abstract long amount();
public static Money create(Currency currency, long amount) {
return new AutoValue_Money(currency, amount);
}
public String displayString() {
return currency().symbol() + amount();
}
}
This alleviates you from having to write any of the boilerplate Parcelable code yourself by generating optimized, tested code for you.
Conclusion⌗
There are several AutoValue extensions already available to help you generate clean code.
Here are just a few examples:
- AutoValue: Gson Extension
- AutoValue: Moshi Extension
- AutoValue: Cursor Extension
- AutoValue: With Extension
- AutoValue: Redacted Extension
If you don’t find one that fits your needs, any of these extensions would make a great starting point to create your own.
In a future blog post I’ll go into detail with the Extension API, looking at how you can create your own AutoValue Extensions, and also dive into these extensions in more detail.