Introduction to AutoValue – 自动增值简介

最后修改: 2016年 7月 30日

1. Overview

1.概述

AutoValue is a source code generator for Java, and more specifically it’s a library for generating source code for value objects or value-typed objects.

AutoValue是一个用于Java的源代码生成器,更确切地说,它是一个用于生成值对象或值类型对象的源代码的库

In order to generate a value-type object all you have to do is to annotate an abstract class with the @AutoValue annotation and compile your class. What is generated is a value object with accessor methods, parameterized constructor, properly overridden toString(), equals(Object) and hashCode() methods.

为了生成一个值型对象,你所要做的就是@AutoValue注解来标注一个抽象类,然后编译你的类。生成的是一个具有访问器方法、参数化构造函数、正确重载的toString()、equals(Object)hashCode()方法的值对象。

The following code snippet is a quick example of an abstract class that when compiled will result in a value object named AutoValue_Person.

下面的代码片段是一个快速的例子,这个抽象类在编译后会产生一个名为AutoValue_Person的价值对象。

@AutoValue
abstract class Person {
    static Person create(String name, int age) {
        return new AutoValue_Person(name, age);
    }

    abstract String name();
    abstract int age();
}

Let’s continue and find out more about value objects, why we need them and how AutoValue can help make the task of generating and refactoring code much less time consuming.

让我们继续了解更多关于价值对象的信息,为什么我们需要它们,以及AutoValue如何帮助生成和重构代码的任务减少时间。

2. Maven Setup

2.Maven的设置

To use AutoValue in a Maven projects, you need to include the following dependency in the pom.xml:

要在Maven项目中使用AutoValue,你需要在pom.xml中包含以下依赖关系。

<dependency>
    <groupId>com.google.auto.value</groupId>
    <artifactId>auto-value</artifactId>
    <version>1.2</version>
</dependency>

The latest version can be found by following this link.

最新版本可以通过这个链接找到。

3. Value-Typed Objects

3.价值类型的对象

Value-types are the end product of library, so to appreciate its place in our development tasks, we must thoroughly understand value-types, what they are, what they are not and why we need them.

价值类型是库的最终产品,所以为了体会它在我们开发任务中的地位,我们必须彻底了解价值类型,它们是什么,它们不是什么,以及为什么我们需要它们。

3.1. What Are Value-Types?

3.1.什么是 “价值类型”?

Value-type objects are objects whose equality to one another is not determined by identity but rather their internal state. This means that two instances of a value-typed object are considered equal as long as they have equal field values.

值型对象是指其相互间的平等性不是由身份决定的,而是由其内部状态决定的。这意味着,只要一个值型对象的两个实例具有相等的字段值,就被认为是相等的。

Typically, value-types are immutable. Their fields must be made final and they must not have setter methods as this will make them changeable after instantiation.

一般来说,值型是不可变的。它们的字段必须是final,它们不能有setter方法,因为这将使它们在实例化后可以被改变。

They must consume all field values through a constructor or a factory method.

它们必须通过构造函数或工厂方法消耗所有的字段值。

Value-types are not JavaBeans because they don’t have a default or zero argument constructor and neither do they have setter methods, similarly, they are not Data Transfer Objects nor Plain Old Java Objects.

Value-types不是JavaBeans,因为它们没有默认或零参数的构造函数,也没有setter方法,同样,它们不是数据传输对象,也不是Plain Old Java对象

Additionally, a value-typed class must be final, so that they are not extendable, least that someone overrides the methods. JavaBeans, DTOs and POJOs need not be final.

此外,一个价值类型的类必须是最终的,这样它们就不能被扩展,至少有人会重写这些方法。JavaBeans、DTO和POJO不需要是最终的。

3.2. Creating a Value-Type

3.2.创建一个价值类型

Assuming we want to create a value-type called Foo with fields called text and number. How would we go about it?

假设我们想创建一个名为Foo的值型,其字段名为textnumber.,我们该怎么做?

We would make a final class and mark all its fields as final. Then we would use the IDE to generate the constructor, the hashCode() method, the equals(Object) method, the getters as mandatory methods and a toString() method, and we would have a class like so:

我们将创建一个最终类,并将其所有字段标记为最终字段。然后我们将使用IDE生成构造函数、hashCode()方法、equals(Object)方法、getters强制方法和toString()方法,然后我们就会有一个这样的类。

public final class Foo {
    private final String text;
    private final int number;
    
    public Foo(String text, int number) {
        this.text = text;
        this.number = number;
    }
    
    // standard getters
    
    @Override
    public int hashCode() {
        return Objects.hash(text, number);
    }
    @Override
    public String toString() {
        return "Foo [text=" + text + ", number=" + number + "]";
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        Foo other = (Foo) obj;
        if (number != other.number) return false;
        if (text == null) {
            if (other.text != null) return false;
        } else if (!text.equals(other.text)) {
            return false;
        }
        return true;
    }
}

After creating an instance of Foo, we expect it’s internal state to remain the same for its entire life cycle.

在创建Foo的实例后,我们希望它的内部状态在整个生命周期中保持不变。

As we will see in the following subsection the hashCode of an object must change from instance to instance, but for value-types, we have to tie it to the fields which define the internal state of the value object.

正如我们将在下面的小节中看到的,一个对象的hashCode必须在不同的实例中改变,但是对于值类型,我们必须将它与定义值对象内部状态的字段联系起来。

Therefore, even changing a field of the same object would change the hashCode value.

因此,即使改变同一对象的一个字段也会改变hashCode值。

3.3. How Value-Types Work

3.3.价值类型是如何工作的

The reason value-types must be immutable is to prevent any change to their internal state by the application after they have been instantiated.

值类型必须是不可变的,原因是为了防止应用程序在它们被实例化后对其内部状态的任何改变。

Whenever we want to compare any two value-typed objects, we must, therefore, use the equals(Object) method of the Object class.

每当我们想比较任何两个值类型的对象时,我们必须使用Object类的equals(Object)方法

This means that we must always override this method in our own value-types and only return true if the fields of the value objects we are comparing have equal values.

这意味着我们必须始终在我们自己的值类型中覆盖这个方法,并且只在我们要比较的值对象的字段有相等的值时返回真。

Moreover, for us to use our value objects in hash-based collections like HashSets and HashMaps without breaking, we must properly implement the hashCode() method.

此外,为了让我们能够在基于哈希的集合(如HashSets和HashMaps)中使用我们的值对象而不被破坏,我们必须正确实现hashCode() 方法

3.4. Why We Need Value-Types

3.4.为什么我们需要价值类型

The need for value-types comes up quite often. These are cases where we would like to override the default behavior of the original Object class.

对值类型的需求经常出现。在这些情况下,我们想覆盖原始Object类的默认行为。

As we already know, the default implementation of the Object class considers two objects equal when they have the same identity however for our purposes we consider two objects equal when they have the same internal state.

我们已经知道,Object类的默认实现认为两个对象在具有相同的身份时是平等的,但是为了我们的目的,我们认为两个对象在具有相同的内部状态时是平等的

Assuming we would like to create a money object as follows:

假设我们想创建一个货币对象,如下所示。

public class MutableMoney {
    private long amount;
    private String currency;
    
    public MutableMoney(long amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }
    
    // standard getters and setters
    
}

We can run the following test on it to test its equality:

我们可以对其运行以下测试,以测试其平等性。

@Test
public void givenTwoSameValueMoneyObjects_whenEqualityTestFails_thenCorrect() {
    MutableMoney m1 = new MutableMoney(10000, "USD");
    MutableMoney m2 = new MutableMoney(10000, "USD");
    assertFalse(m1.equals(m2));
}

Notice the semantics of the test.

注意这个测试的语义。

We consider it to have passed when the two money objects are not equal. This is because we have not overridden the equals method so equality is measured by comparing the memory references of the objects, which of course are not going to be different because they are different objects occupying different memory locations.

当两个货币对象不相等时,我们认为它已经通过了。这是因为我们没有重写equals方法,所以平等性是通过比较对象的内存引用来衡量的,当然,这些引用不会不同,因为它们是占据不同内存位置的不同对象。

Each object represents 10,000 USD but Java tells us our money objects are not equal. We want the two objects to test unequal only when either the currency amounts are different or the currency types are different.

每个对象代表10,000美元,但Java告诉我们,我们的货币对象是不相等的。我们希望这两个对象只有在货币金额不同或者货币类型不同的情况下才能测试出不相等。

Now let us create an equivalent value object and this time we will let the IDE generate most of the code:

现在让我们创建一个等价的值对象,这次我们将让IDE生成大部分的代码。

public final class ImmutableMoney {
    private final long amount;
    private final String currency;
    
    public ImmutableMoney(long amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (amount ^ (amount >>> 32));
        result = prime * result + ((currency == null) ? 0 : currency.hashCode());
        return result;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        ImmutableMoney other = (ImmutableMoney) obj;
        if (amount != other.amount) return false;
        if (currency == null) {
            if (other.currency != null) return false;
        } else if (!currency.equals(other.currency))
            return false;
        return true;
    }
}

The only difference is that we overrode the equals(Object) and hashCode() methods, now we have control over how we want Java to compare our money objects. Let’s run its equivalent test:

唯一的区别是,我们覆盖了 equals(Object)hashCode()方法,现在我们可以控制我们希望Java如何比较我们的货币对象。让我们来运行它的等价测试。

@Test
public void givenTwoSameValueMoneyValueObjects_whenEqualityTestPasses_thenCorrect() {
    ImmutableMoney m1 = new ImmutableMoney(10000, "USD");
    ImmutableMoney m2 = new ImmutableMoney(10000, "USD");
    assertTrue(m1.equals(m2));
}

Notice the semantics of this test, we expect it to pass when both money objects test equal via the equals method.

注意这个测试的语义,当两个货币对象通过equals方法测试相等时,我们期望它能通过。

4. Why AutoValue?

4.为什么是AutoValue?

Now that we thoroughly understand value-types and why we need them, we can look at AutoValue and how it comes into the equation.

现在我们已经彻底了解了价值类型以及为什么我们需要它们,我们可以看看AutoValue以及它是如何进入方程式的。

4.1. Issues With Hand-Coding

4.1.手工编码的问题

When we create value-types like we have done in the preceding section, we will run into a number of issues related to bad design and a lot of boilerplate code.

当我们像上一节那样创建价值类型时,我们会遇到一些与糟糕的设计和大量的模板代码有关的问题

A two field class will have 9 lines of code: one for package declaration, two for the class signature and its closing brace, two for field declarations, two for constructors and its closing brace and two for initializing the fields, but then we need getters for the fields, each taking three more lines of code, making six extra lines.

一个双字段的类将有9行代码:1行用于包的声明,2行用于类的签名和封闭括号,2行用于字段的声明,2行用于构造函数和封闭括号,2行用于初始化字段,但随后我们需要字段的获取器,每个获取器又需要3行代码,这样就多了6行。

Overriding the hashCode() and equalTo(Object) methods require about 9 lines and 18 lines respectively and overriding the toString() method adds another five lines.

重写hashCode()equalTo(Object)方法分别需要大约9行和18行,重写toString()方法又增加了5行。

That means a well-formatted code base for our two field class would take about 50 lines of code.

这意味着为我们的两个字段类提供一个格式良好的代码库将需要大约50行代码

4.2. IDEs to the Rescue?

4.2.IDEs to the Rescue?

This is is easy with an IDE like Eclipse or IntilliJ and with only one or two value-typed classes to create. Think about a multitude of such classes to create, would it still be as easy even if the IDE helps us?

这在Eclipse或IntilliJ这样的IDE中是很容易的,而且只需要创建一两个值类型的类。想想看,如果要创建许多这样的类,即使IDE帮助我们,还能这么容易吗?

Fast forward, some months down the road, assume we have to revisit our code and make amendments to our Money classes and perhaps convert the currency field from the String type to another value-type called Currency.

快进,几个月后,假设我们必须重新审视我们的代码,并对我们的Money类进行修正,或许将currency字段从String类型转换为另一个名为Currency的值类型。

4.3. IDEs Not Really So Helpful

4.3.集成开发环境并非真的那么有帮助

An IDE like Eclipse can’t simply edit for us our accessor methods nor the toString(), hashCode() or equals(Object) methods.

像Eclipse这样的IDE不能简单地为我们编辑访问器方法,也不能编辑toString()hashCode()equals(Object)方法。

This refactoring would have to be done by hand. Editing code increases the potential for bugs and with every new field we add to the Money class, the number of lines increases exponentially.

这种重构必须由手工完成。编辑代码会增加出现错误的可能性,而且我们在Money类中每增加一个新字段,行数就会成倍增加。

Recognizing the fact that this scenario happens, that it happens often and in large volumes will make us really appreciate the role of AutoValue.

认识到这种情况的发生,以及它经常和大量发生的事实,将使我们真正理解AutoValue的作用。

5. AutoValue Example

5.自动增值实例

The problem AutoValue solves is to take all the boilerplate code that we talked about in the preceding section, out of our way so that we never have to write it, edit it or even read it.

AutoValue解决的问题是将我们在上一节中谈到的所有模板代码从我们的方式中移除,这样我们就不必写它、编辑它甚至阅读它。

We will look at the very same Money example, but this time with AutoValue. We will call this class AutoValueMoney for the sake of consistency:

我们将看看同样的Money例子,但这次是用AutoValue。为了一致起见,我们将称这个类为AutoValueMoney

@AutoValue
public abstract class AutoValueMoney {
    public abstract String getCurrency();
    public abstract long getAmount();
    
    public static AutoValueMoney create(String currency, long amount) {
        return new AutoValue_AutoValueMoney(currency, amount);
    }
}

What has happened is that we write an abstract class, define abstract accessors for it but no fields, we annotate the class with @AutoValue all totalling to only 8 lines of code, and javac generates a concrete subclass for us which looks like this:

现在的情况是,我们写了一个抽象的类,为它定义了抽象的访问器,但没有字段,我们用@AutoValue来注释这个类,总共只有8行代码,而javac为我们生成了一个具体的子类,看起来像这样。

public final class AutoValue_AutoValueMoney extends AutoValueMoney {
    private final String currency;
    private final long amount;
    
    AutoValue_AutoValueMoney(String currency, long amount) {
        if (currency == null) throw new NullPointerException(currency);
        this.currency = currency;
        this.amount = amount;
    }
    
    // standard getters
    
    @Override
    public int hashCode() {
        int h = 1;
        h *= 1000003;
        h ^= currency.hashCode();
        h *= 1000003;
        h ^= amount;
        return h;
    }
    
    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (o instanceof AutoValueMoney) {
            AutoValueMoney that = (AutoValueMoney) o;
            return (this.currency.equals(that.getCurrency()))
              && (this.amount == that.getAmount());
        }
        return false;
    }
}

We never have to deal with this class directly at all, neither do we have to edit it when we need to add more fields or make changes to our fields like the currency scenario in the previous section.

我们根本不需要直接处理这个类,当我们需要添加更多字段或对字段进行修改时,也不需要编辑它,比如上一节的货币方案。

Javac will always regenerate updated code for us.

Javac将始终为我们重新生成更新的代码

While using this new value-type, all callers see is only the parent type as we will see in the following unit tests.

当使用这个新的值类型时,所有调用者看到的只是父类型,我们将在下面的单元测试中看到。

Here is a test that verifies that our fields are being set correctly:

这里有一个测试,验证我们的字段是否被正确设置。

@Test
public void givenValueTypeWithAutoValue_whenFieldsCorrectlySet_thenCorrect() {
    AutoValueMoney m = AutoValueMoney.create("USD", 10000);
    assertEquals(m.getAmount(), 10000);
    assertEquals(m.getCurrency(), "USD");
}

A test to verify that two AutoValueMoney objects with the same currency and same amount test equal follow:

一个测试来验证两个具有相同货币和相同金额的AutoValueMoney对象测试是否相等。

@Test
public void given2EqualValueTypesWithAutoValue_whenEqual_thenCorrect() {
    AutoValueMoney m1 = AutoValueMoney.create("USD", 5000);
    AutoValueMoney m2 = AutoValueMoney.create("USD", 5000);
    assertTrue(m1.equals(m2));
}

When we change the currency type of one money object to GBP, the test: 5000 GBP == 5000 USD is no longer true:

当我们把一个货币对象的货币类型改为GBP时,测试: 5000 GBP == 5000 USD 不再是真的。

@Test
public void given2DifferentValueTypesWithAutoValue_whenNotEqual_thenCorrect() {
    AutoValueMoney m1 = AutoValueMoney.create("GBP", 5000);
    AutoValueMoney m2 = AutoValueMoney.create("USD", 5000);
    assertFalse(m1.equals(m2));
}

6. AutoValue With Builders

6.与建设者的自动价值

The initial example we have looked at covers the basic usage of AutoValue using a static factory method as our public creation API.

我们所看的最初的例子涵盖了AutoValue的基本用法,使用静态工厂方法作为我们的公共创建API。

Notice that if all our fields were Strings, it would be easy to interchange them as we passed them to the static factory method, like placing the amount in the place of currency and vice versa.

注意,如果我们所有的字段都是字符串当我们将它们传递给静态工厂方法时,很容易将它们互换,比如将amount放在currency的位置,反之亦然。

This is especially likely to happen if we have many fields and all are of String type. This problem is made worse by the fact that with AutoValue, all fields are initialized through the constructor.

如果我们有很多字段,而且都是String类型,这种情况就特别容易发生。由于使用AutoValue,所有字段都是通过构造函数初始化的,这个问题就变得更加严重。

To solve this problem we should use the builder pattern. Fortunately. this can be generated by AutoValue.

为了解决这个问题,我们应该使用builder模式。幸运的是,这可以由AutoValue生成。

Our AutoValue class does not really change much, except that the static factory method is replaced by a builder:

我们的AutoValue类其实并没有什么变化,只是静态工厂方法被一个构建器所取代。

@AutoValue
public abstract class AutoValueMoneyWithBuilder {
    public abstract String getCurrency();
    public abstract long getAmount();
    static Builder builder() {
        return new AutoValue_AutoValueMoneyWithBuilder.Builder();
    }
    
    @AutoValue.Builder
    abstract static class Builder {
        abstract Builder setCurrency(String currency);
        abstract Builder setAmount(long amount);
        abstract AutoValueMoneyWithBuilder build();
    }
}

The generated class is just the same as the first one but a concrete inner class for the builder is generated as well implementing the abstract methods in the builder:

生成的类与第一个类相同,但也为构建器生成了一个具体的内部类,实现了构建器中的抽象方法。

static final class Builder extends AutoValueMoneyWithBuilder.Builder {
    private String currency;
    private long amount;
    Builder() {
    }
    Builder(AutoValueMoneyWithBuilder source) {
        this.currency = source.getCurrency();
        this.amount = source.getAmount();
    }
    
    @Override
    public AutoValueMoneyWithBuilder.Builder setCurrency(String currency) {
        this.currency = currency;
        return this;
    }
    
    @Override
    public AutoValueMoneyWithBuilder.Builder setAmount(long amount) {
        this.amount = amount;
        return this;
    }
    
    @Override
    public AutoValueMoneyWithBuilder build() {
        String missing = "";
        if (currency == null) {
            missing += " currency";
        }
        if (amount == 0) {
            missing += " amount";
        }
        if (!missing.isEmpty()) {
            throw new IllegalStateException("Missing required properties:" + missing);
        }
        return new AutoValue_AutoValueMoneyWithBuilder(this.currency,this.amount);
    }
}

Notice also how the test results don’t change.

还请注意,测试结果并没有改变。

If we want to know that the field values are actually correctly set through the builder, we can execute this test:

如果我们想知道字段值是否真的通过构建器正确设置,我们可以执行这个测试。

@Test
public void givenValueTypeWithBuilder_whenFieldsCorrectlySet_thenCorrect() {
    AutoValueMoneyWithBuilder m = AutoValueMoneyWithBuilder.builder().
      setAmount(5000).setCurrency("USD").build();
    assertEquals(m.getAmount(), 5000);
    assertEquals(m.getCurrency(), "USD");
}

To test that equality depends on internal state:

要检验这种平等取决于内部状态。

@Test
public void given2EqualValueTypesWithBuilder_whenEqual_thenCorrect() {
    AutoValueMoneyWithBuilder m1 = AutoValueMoneyWithBuilder.builder()
      .setAmount(5000).setCurrency("USD").build();
    AutoValueMoneyWithBuilder m2 = AutoValueMoneyWithBuilder.builder()
      .setAmount(5000).setCurrency("USD").build();
    assertTrue(m1.equals(m2));
}

And when the field values are different:

而当字段值不同时。

@Test
public void given2DifferentValueTypesBuilder_whenNotEqual_thenCorrect() {
    AutoValueMoneyWithBuilder m1 = AutoValueMoneyWithBuilder.builder()
      .setAmount(5000).setCurrency("USD").build();
    AutoValueMoneyWithBuilder m2 = AutoValueMoneyWithBuilder.builder()
      .setAmount(5000).setCurrency("GBP").build();
    assertFalse(m1.equals(m2));
}

7. Conclusion

7.结论

In this tutorial, we have introduced most of the basics of Google’s AutoValue library and how to use it to create value-types with a very little code on our part.

在本教程中,我们已经介绍了谷歌的AutoValue库的大部分基础知识,以及如何使用它来创建价值类型,而我们的代码很少。

An alternative to Google’s AutoValue is the Lombok project – you can have a look at the introductory article about using Lombok here.

Google的AutoValue的一个替代方案是Lombok项目 – 你可以看看关于使用Lombok的介绍性文章

The full implementation of all these examples and code snippets can be found in the AutoValue GitHub project.

所有这些例子和代码片断的完整实现可以在AutoValue GitHub项目中找到。