AutoValue —— Generated immutable value classes


本文參考

在《Effective Java》第三版第十條"Obey the general contract when overriding equals"中提到google的AutoValue框架能夠自動生成equals()方法,實際上這個框架的作用不僅僅限於生成equals()方法那么簡單,它還能夠使值類通過靜態工廠方法構建實例,並實現Builder構建者模式,省去了程序員對值類的重復性工作

github地址:https://github.com/google/auto/blob/master/value/userguide/index.md

環境

idea 2020.1 + AutoValue 1.7.1

Maven配置

我們需要同時配置auto-value-annotations和auto-value兩個dependency

<dependency>
  <groupId>com.google.auto.value</groupId>
  <artifactId>auto-value-annotations</artifactId>
  <version>1.7.1</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>com.google.auto.value</groupId>
  <artifactId>auto-value</artifactId>
  <version>1.7.1</version>
  <optional>true</optional>
  <scope>provided</scope>
</dependency>

 

Idea配置

在Build,Execution,Deployment -> Complier -> Annotation Processor中勾選Enable annotation processing

默認的Production source directory和Test sources directory不需要更改

 

基本用法

如下代碼所示,構造Person抽象類,create()靜態工廠方法,和抽象的字段方法

@AutoValue
abstract class Person {
  abstract String getName();
  abstract int getAge();

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

此時顯然還沒有AutoValue_Person這個類(固定的前綴寫法),因此idea會報錯,但是我們暫時不用擔心這個問題

編寫一個簡單的測試類,測試方法如下

@Test
public void test() {
  Person person = Person.create("kuluo", 18);
  assertEquals("kuluo", person.name());
}

可以先對源代碼進行編譯而不運行,編譯結束后可以看到AutoValue_Person的報錯消失

我們可以在默認的target\generated-sources\annotations或target\generated-test-sources\test-annotations文件夾中看到生成的值類

@Generated("com.google.auto.value.processor.AutoValueProcessor")
final class AutoValue_Person extends Person {
  private final String name;
  private final int age;

  AutoValue_Person(String name, int age) {
    if (name == null) {
      throw new NullPointerException("Null name");
    }
    this.name = name;
    this.age = age;
  }

  @Override
  String getName() {
    return name;
  }

  @Override
  int getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "Person{"
           + "name=" + name + ", "
           + "age=" + age
           + "}";
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (o instanceof Person) {
      Person that = (Person) o;
      return this.name.equals(that.name()) && this.age == that.age();
    }
    return false;
  }
  
  @Override
  public int hashCode() {
    int h$ = 1;
    h$ *= 1000003;
    h$ ^= name.hashCode();
    h$ *= 1000003;
    h$ ^= age;
    return h$;
  }
}

注意:

  • 值類被聲明為final類型,無法再被繼承
  • 值類不具備setter方法,實例被創建后就無法被更改
  • 若在構造實例時允許傳入可變類型的值,如List<String>和String[],則需要在Guava中選擇對應的不可變類型,並更改create()靜態工廠方法

check if the mutable type has a corresponding immutable cousin. For example, the types List<String> and String[] have the immutable counterpart ImmutableList<String> in Guava. If so, use the immutable type for your property, and only accept the mutable type during construction

@AutoValue
abstract class Person {
  abstract ImmutableList<String> getName();
  abstract int getAge();

  static Person create(List<String> name, int age) {
    return new AutoValue_Person(ImmutableList.copyOf(name), age);
  }
}

  • 值類在構建實例時會檢查每一個字段是否為null,若某字段為null,則拋出空指針異常
  • 若允許某個字段為null,則必須在抽象類create()靜態工廠方法的聲明中,為該字段和它對應的getter方法同時添加@Nullable注解

if @Nullable is only added to the parameter in create (or similarly the setter method of AutoValue.Builder), but not the corresponding accessor method, it won't have any effect.

@AutoValue
abstract class Person {
  @Nullable abstract String getName();
  abstract int getAge();

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

下面僅展示發生變化的方法,我們可以看到在equals()方法和hashCode()方法中也自動增加了對null的判斷

AutoValue_Person(@Nullable String name, int age) {
  this.name = name;
  this.age = age;
}

@Nullable
@Override
String getName() {
  return name;
}

@Override
public boolean equals(Object o) {
  if (o == this) {
    return true;
  }
  if (o instanceof Person) {
    Person that = (Person) o;
    return (this.name == null ? that.getName() == null : this.name.equals(that.getName()))
            && this.age == that.getAge();
  }
  return false;
}

@Override
public int hashCode() {
  int h$ = 1;
  h$ *= 1000003;
  h$ ^= (name == null) ? 0 : name.hashCode();
  h$ *= 1000003;
  h$ ^= age;
  return h$;
}

 

Builder構建者模式用法

涉及抽象靜態內部類Builder,並為它添加@AutoValue.Builder注解

@AutoValue
abstract class PersonWithBuilder {
  abstract String getName();
  abstract int getAge();

  static Builder builder() {
    return new AutoValue_PersonWithBuilder.Builder();
  }

  @AutoValue.Builder
  abstract static class Builder {
    abstract Builder name(String name);
    abstract Builder age(int age);
    abstract PersonWithBuilder build();
  }
}

編寫一個簡單的測試類,測試代碼如下:

@Test
public void testWithBuilder() {
  PersonWithBuilder personWithBuilder = PersonWithBuilder
                .builder()
                .name("kuluo")
                .age(18)
                .build();
  assertEquals("kuluo", personWithBuilder.getName());
  assertEquals(18, personWithBuilder.getAge());
}

編譯運行后生成AutoValue_PersonWithBuilder類

@Generated("com.google.auto.value.processor.AutoValueProcessor")
final class AutoValue_PersonWithBuilder extends PersonWithBuilder {
  private final String name;
  private final int age;

  private AutoValue_PersonWithBuilder(String nameint age) {
    this.name = name;
    this.age = age;
  }

  @Override
  String getName() { return name; }

  @Override
  int getAge() { return age; }

  @Override
  public String toString() {
    return "PersonWithBuilder{"
           + "name=" + name + ", "
           + "age=" + age
           + "}";
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) { return true; }
    if (o instanceof PersonWithBuilder) {
      PersonWithBuilder that = (PersonWithBuilder) o;
      return this.name.equals(that.getName()) && this.age == that.getAge();
    }
    return false;
  }

  @Override
  public int hashCode() {
    int h$ = 1;
    h$ *= 1000003;
    h$ ^= name.hashCode();
    h$ *= 1000003;
    h$ ^= age;
    return h$;
  }

  static final class Builder extends PersonWithBuilder.Builder {
    private String name;
    private Integer age;
    Builder() {
    }
    @Override
    PersonWithBuilder.Builder name(String name) {
      if (name == null) {
        throw new NullPointerException("Null name");
      }
      this.name = name;
      return this;
    }
    @Override
    PersonWithBuilder.Builder age(int age) {
      this.age = age;
      return this;
    }
    @Override
    PersonWithBuilder build() {
      String missing = "";
      if (this.name == null) {
        missing += " name";
      }
      if (this.age == null) {
        missing += " age";
      }
      if (!missing.isEmpty()) {
        throw new IllegalStateException("Missing required properties:" + missing);
      }
      return new AutoValue_PersonWithBuilder(this.name, this.age);
    }
  }
}

注意:

  • 值類在構建實例時會在build()方法內檢查每一個字段是否為null,若某字段為null,則拋出空指針異常
  • 若允許某個字段為null,則必須同時在抽象靜態內部類的"setter"方法的形參和外側的"getter"方法同時添加@Nullable注解

@AutoValue
abstract class PersonWithBuilder {
  @Nullable abstract String getName();
  abstract int getAge();

  static Builder builder() {
    return new AutoValue_PersonWithBuilder.Builder();
  }

  @AutoValue.Builder
  abstract static class Builder {
    abstract Builder name(@Nullable String name);
    abstract Builder age(int age);
    abstract PersonWithBuilder build();
  }
}

我們可以在生成的AutoValue_PersonWithBuilder類中看到已經沒有了對name的null判斷

@Override
PersonWithBuilder build() {
  String missing = "";
  if (this.age == null) {
    missing += " age";
  }
  if (!missing.isEmpty()) {
    throw new IllegalStateException("Missing required properties:" + missing);
  }
  return new AutoValue_PersonWithBuilder(this.name, this.age);
}

  • 若需要為某字段設置默認值,僅需在builder()方法中調用Builder的"setter"方法

@AutoValue
abstract class PersonWithBuilder {
  abstract String getName();
  abstract int getAge();

  static Builder builder() {
    return new AutoValue_PersonWithBuilder.Builder().name("kuluo");
  }

  @AutoValue.Builder
  abstract static class Builder {
    abstract Builder name(String name);
    abstract Builder age(int age);
    abstract PersonWithBuilder build();
  }
}

  • 使用Builder模式后會屏蔽靜態工廠方法,若一定要使用靜態工廠方法,則需要在靜態工廠方法內調用Builder靜態內部類來創建實例,而不是私有的構造方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM