本文参考

在《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. vim中的incsearch不好用,没有动态效果,为什么——incsearch is not working

    虽然使用Linux好多年了,使用vim也好多年了,但是使用vim进行search的时候重来也没有想过要添加动态效果,近来突然有了兴致想要添加这个功能,不过发现居然不好用,在百度上找了好长时间居然没有一 ...

  2. 【C# 线程】interLocked锁

    overview 同步基元分为用户模式和内核模式 用户模式:Iterlocked.Exchange(互锁).SpinLocked(自旋锁).易变构造(volatile关键字.volatile类.Thr ...

  3. python-can库基于PCAN-USB使用方法

    一.概述 1.介绍 python-can库为Python提供了控制器局域网的支持,为不同的硬件设备提供了通用的抽象,并提供了一套实用程序,用于在CAN总线上发送和接收消息. 支持硬件接口: Name ...

  4. Objective-C 基础教程第五章,复合

    目录 Objective-C 基础教程第五章,复合 什么是复合? Car程序 自定义NSLog() 存取方法get Set Tires(轮胎) 存取方法 Car类代码的其他变化 扩展Car程序 复合还 ...

  5. linux多进/线程编程(7)——多线程1(线程的创建,回收,分离,设置线程属性等)

    参考资料: 1.博客1:https://blog.csdn.net/zhou1021jian/article/details/71531699 2.博客2:https://blog.csdn.net/ ...

  6. python面试_总结02_代码题

    - 代码题 1.创建一个函数,接收一个字符串参数,判断其做为Python标识符是否合法. 具体要求: 如果合法则输出 True,否则输出 False. 如果该字符串与Python内置的关键字,或Bif ...

  7. JZ-024-二叉树中和为某一值的路径

    二叉树中和为某一值的路径 题目描述 输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径. 题目链 ...

  8. 简单聊一聊Javascript中的模块化

    在面试中只要说到模块化的问题,多多少少总会问到这些,umd.amd.cjs.esm,可能听过其中一个两个,或者都听说过.接下来我们先简单了解一下他们到底是什么,又有什么样的区别呢. 最开始的时候,Ja ...

  9. C# ProgressBar的简单使用

    ProgressBar控件(进度条)用于在win窗体中显示进度,由于它的值会不断更新,为了不让界面假死,一般都是采用多线程的方式对进度条进行管理.有关ProgressBar的理论基础跟详细知识我在这里 ...

  10. [动态规划] LeetCode 2055. 蜡烛之间的盘子

    LeetCode 2055 蜡烛之间的盘子 前言: 这个题做的时间略长了,开始的时候打算先定位两个端点的蜡烛,之后在遍历其中的盘子,结果不言而喻,必time limit了,之后就预处理了前x的蜡烛间盘 ...