本文参考

在《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 name, int 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静态内部类来创建实例,而不是私有的构造方法

AutoValue —— Generated immutable value classes的更多相关文章

  1. 针对android方法数64k的限制,square做出的努力。精简protobuf

    1.早期的Dalvik VM内部使用short类型变量来标识方法的id,dex限制了程序的最大方法数是65535,如果超过最大限制,无法编译,把dex.force.jumbo=true添加到proje ...

  2. RPC服务框架探索之Thrift

    前言架构服务化后,需要实现一套方便调用各服务的框架,现在开源如日中天,优先会寻找开源实现,如果没有合适自家公司业务的,才会考虑从零开发,尤其是一切以KPI为准绳的公司,谁会跟钱过不去?N个月之前,公司 ...

  3. 【译】Android API 规范

    [译]Android API 规范 译者按: 修改R代码遇到Lint tool的报错,搜到了这篇文档,aosp仓库地址:Android API Guidelines. 58e9b5f Project ...

  4. PHP Excel 下载数据,并分页下载

    直接上代码: 调用下载Excel: $total=$duoduo->count(MOD.' as a',$where); $objExcel= SelfExcelObject(); //导出 i ...

  5. PHP执行文档操作

    1.POWINTPOINT系列 之前参与过一个商城的项目,里面有将excel 导出的功能,但是如果要弄成PPT的我们应该怎么办呢?PHP是属于服务器端的 总不能在里面装个Powintpoint吧.于是 ...

  6. PHP导入导出excel表格图片(转)

    写excel的时候,我用过pear的库,也用过pack压包的头,同样那些利用smarty等作的简单替换xml的也用过,csv的就更不用谈了.呵呵.(COM方式不讲了,这种可读的太多了,我也写过利用wp ...

  7. PHPExcel 大数据的导出

    PHPExcel 是一个php语言读取导出数据.导入生成Excel的类库,使用起来非常方便,但有时会遇到以些问题,比如导出的数据超时,内存溢出等. 下面我们来说说这些问题和解决办法. PHPExcel ...

  8. Beginning Scala study note(6) Scala Collections

    Scala's object-oriented collections support mutable and immutable type hierarchies. Also support fun ...

  9. 黄聪:phpexcel中文教程-设置表格字体颜色背景样式、数据格式、对齐方式、添加图片、批注、文字块、合并拆分单元格、单元格密码保护

    首先到phpexcel官网上下载最新的phpexcel类,下周解压缩一个classes文件夹,里面包含了PHPExcel.php和PHPExcel的文件夹,这个类文件和文件夹是我们需要的,把class ...

随机推荐

  1. Java的诞生历史

    Java帝国的诞生 1.1972年C诞生 贴近硬件,运行极快,效率极高 操作系统,编译器,数据库,网络系统等都采用C语言 但是,它的指针和内存管理给程序员安上了"枷锁".它的指针没 ...

  2. 【CPU】进程管理之五状态模型

    本文为第三篇,进程管理之五状态模型,进程在操作系统里边是有多个状态的,本文就是了解进程在操作系统中的多个状态 1.进程的五个状态 创建状态 就绪状态 阻塞状态 执行状态 终止状态 2.进程处于这五种状 ...

  3. 由浅入深--MyBatis整体架构

    学一门技术,做一件事不应该马上就直接进入到细节中,而是应该鸟瞰其全貌,了解它的整体架构和执行流程. MyBatis的整体架构 MyBatis的整体架构分为三层,分别是基础支持层,核心处理层和接口层,各 ...

  4. /etc/fstab文件的详解

    转至:https://blog.csdn.net/youmatterhsp/article/details/83933158 一./etc/fstab文件的作用 磁盘被手动挂载之后都必须把挂载信息写入 ...

  5. AcWing 288. 休息时间

    传送门 思路: 考虑DP,设dp[i][j][1]为牛在前小时休息j个小时且第i个小时休息时,回复的最多体力:dp[i][j][0]为牛在前小时休息j个小时且第i个小时没有休息时,回复的最多体力. 可 ...

  6. PyTorch深度学习实践——处理多维特征的输入

    处理多维特征的输入 课程来源:PyTorch深度学习实践--河北工业大学 <PyTorch深度学习实践>完结合集_哔哩哔哩_bilibili 这一讲介绍输入为多维数据时的分类. 一个数据集 ...

  7. 01_opencv_python_基本图像处理

    1  图像基本操作   1.0.1  环境配置地址: Anaconda:https://www.anaconda.com/download/ Python_whl:https://www.lfd.uc ...

  8. 关于SQL优化的辟谣

    列举一些关于 SQL 语句的谣言,避免一些生瓜蛋子面试的时候被另外一群生瓜蛋子的 SQL 优化宝典给坑了. 以下所有内容都是 SQL Server 中的,其他数据库只能参考和借鉴 一.全表扫描 全表扫 ...

  9. Java 类方法和类变量

    目录 一.类变量 1.如果定义类变量 2.如何访问类变量 3.类变量的使用注意事项和细节 二.类方法 1.类方法的形式 2.类方法的调用 3.类方法经典使用场景 4.类变量和类方法 三.main方法 ...

  10. 别再问WiFi密码了,HMS Core统一扫码服务让手机一键联网

    现代生活离不开网络.在餐厅.商场等公共场所,手机连接WiFi一直是高频使用场景.虽然公共场所的免费WiFi越来越多,但网络连接过程却很麻烦.有的需要打开网页注册或点击广告链接才能上网,还有的要求下载特 ...