这些极简的注解你都清楚吗

lombok 是一个非常神奇的 java 类库,会利用注解自动生成 java Bean 中烦人的 Getter、Setter,还能自动生成 logger、ToString、HashCode、Builder 等 java 特色的函数或是符合设计模式的函数,能够让你 java Bean 更简洁,更美观。lombok 的思想非常先进,它让我们省略繁琐的样板代码,不要在重复的代码上花费太长时间,它也是Java语言演进过程中必然出现的一种思想,要用20% 的时间做 80%的事情。下面就来看一下 lombok 的具体用法。

@Data

@Data 是一个很方便的注解,它和@ToString@EqualAndHashCode@Getter/@Setter、和@RequiredArgsConstructor 绑定在一起。换句话说,@Data 生成通常与简单的POJO(Plain Old Java Objects) 和 bean 相关联的所有样板代码,例如:获取所有的属性,设置所有不可继承的属性,适应toString、equals 和 hashcode 的实现,通过构造方法初始化所有final 的属性,以及所有没有使用@NonNull标记的初始化程序的非final字段,以确保该字段永远不为null。

@Data 就像在类上隐含使用 @toString 、 @EqualAndHashCode、 @Getter、 @Setter 和 @RequiredArgsConstructor 注释一样。@Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor

但是,@Data 无法设置这些注解的参数,例如callSuper、includeFieldNames 和 exclude

如果您需要为这些参数中的任何一个设置非默认值,只需显式添加这些注释;

生成的所有getters/setters 默认都是public 的,为了覆盖访问级别,请使用显式的@Setter \ @Getter批注对字段或类进行注释。你可以使用这个注释(通过与 AccessLevel.NONE结合)来禁止使用 getter或setter。

所有使用 transient 标记的字段都不会视为 hashcode 和 equals。将完全跳过所有静态字段(不考虑任何生成的方法,并且不会为它们创建setter / getter)。

如果类已经包含与通常生成的任何方法具有相同名称和参数计数的方法,则不会生成该方法,也不会发出警告或错误。例如:如果你使用 equals 标记了一个方法,那么不会再生成 equals 方法,即使从技术上讲,由于具有不同的参数类型,它可能是完全不同的方法。同样的规则适用于构造函数(任何显式构造函数都会阻止 @Data 生成一个),以及toString,equals和所有getter和setter。您可以使用@ lombok.experimental.Tolerate 标记任何构造函数或方法,以将它们隐藏在 lombok 中

例如:

import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
import lombok.ToString;

@Data
public class DataExample {

    private final String name;

    @Setter(AccessLevel.PACKAGE)
    private int age;

    private double score;

    private String[] tags;

    @ToString(includeFieldNames = true)
    @Data(staticConstructor = "of")
    public static class Exercise<T> {
        private final String name;
        private final T value;
    }

}

就相当于是不用 lombok 的如下示例:

import java.util.Arrays;

public class DataExample {
  private final String name;
  private int age;
  private double score;
  private String[] tags;

  public DataExample(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  void setAge(int age) {
    this.age = age;
  }

  public int getAge() {
    return this.age;
  }

  public void setScore(double score) {
    this.score = score;
  }

  public double getScore() {
    return this.score;
  }

  public String[] getTags() {
    return this.tags;
  }

  public void setTags(String[] tags) {
    this.tags = tags;
  }

  @Override public String toString() {
    return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
  }

  protected boolean canEqual(Object other) {
    return other instanceof DataExample;
  }

  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof DataExample)) return false;
    DataExample other = (DataExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }

  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.getScore());
    result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
    result = (result*PRIME) + this.getAge();
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
    return result;
  }

  public static class Exercise<T> {
    private final String name;
    private final T value;

    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }

    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }

    public String getName() {
      return this.name;
    }

    public T getValue() {
      return this.value;
    }

    @Override public String toString() {
      return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")";
    }

    protected boolean canEqual(Object other) {
      return other instanceof Exercise;
    }

    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Exercise)) return false;
      Exercise<?> other = (Exercise<?>) o;
      if (!other.canEqual((Object)this)) return false;
      if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
      if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
      return true;
    }

    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
      result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode());
      return result;
    }
  }
}

@NonNull

你可以使用 @NonNull 对方法或者构造器生成 null - check

如果lombok为您生成整个方法或构造函数(例如@Data),Lombok总是将字段上通常称为@NonNull的各种注释视为生成空值检查的信号。但是,现在,在参数上使用lombok自己的@lombok.NonNull会导致在您自己的方法或构造函数中只插入null-check语句。

Null - Check 语句看起来像是如下语句

if(param == null){
  throw new NullPointerException("param is marked @NonNull but is null")
}

这条判空语句会在方法的最开始进行判断

public class NonNullExample {

    @Getter
    private String name;

    public NonNullExample(@NonNull String name){
        this.name = name;
    }
}

这个加上 @NonNull 判空的代码就相当于如下代码

import lombok.NonNull;

public class NonNullExample {
  private String name;

  public NonNullExample(@NonNull String name) {
    if (name == null) {
      throw new NullPointerException("name is marked @NonNull but is null");
    }
    this.name = name;
  }
}

@Getter & @Setter

你可以使用 @Getter 和 @Setter 自动生成任何 getter/setter。

默认的 getter 只返回字段的名称,如果字段的名称为 foo,则返回的是 getFoo(),如果字段类型为 boolean ,则返回 isFoo()。如果字段为 foo 的话,默认的 setter 返回 setFoo,并且类型是 void ,并且带有一个和该属性相同的字段作为参数,用于为此属性字段进行赋值。

除非你指定AccessLevel 访问级别,否则使用 Getter / Setter 生成的方法默认是 public 的作用范围。AccessLevel的访问级别有 PUBLIC, PROTECTED, PACKAGE, and PRIVATE.

你也可以在类上使用 @Getter / @Setter ,在这种情况下,就会对该类中的所有非静态属性生成 get and set 方法

你也可以通过设置 AccessLevel.NONE 禁用任何 get and set 方法的生成。这会使 @Data、@Getter / @Setter 的注解失效。

public class GetterSetterExample {

    @Setter
    @Getter
    private int age = 10;

    @Setter(AccessLevel.PROTECTED)
    private String name;

    @Getter(AccessLevel.PRIVATE)
    private String high;
}

就等同于

public class GetterSetterExample {

  private int age = 10;

  private String name;

  private String high;

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  protected void setName(String name) {
    this.name = name;
  }

  private String getHigh(){
    return high;
  }
}

@ToString

@ToString 注解用来替换掉生成 toString() 方法的实现,默认情况下,它会按顺序打印你的班级名称以及每个字段,并以逗号分隔。

通过设置 includeFieldNames = true 能够使 toString() 方法打印每个字段的属性值和名称。

默认情况下,所有非静态属性都被打印,如果你想要排除某些字段的话,需要设置 @ToString.Exclude,或者,你可以指定ToString(onlyExplicitlyIncluded = true)来指定哪些你希望使用的字段。然后使用@ ToString.Include标记要包含的每个字段。

通过设置 callSuper 为 true ,可以将toString的超类实现的输出包含到输出中。请注意,java.lang.Object 的 toString() 实现没有任何意义,所以你可能不会这样做除非你想要扩展另一个类。

你还可以在toString 中包含方法调用的输出。只能包含不带参数的实例(非静态)方法,为此,请使用@ ToString.Include标记方法。

你可以使用 @ToString.Include(name =“some other name”)更改用于标识成员的名称,并且可以通过 @ ToString.Include(rank = -1)更改成员的打印顺序。没有定义等级的成员默认是0级,等级高的成员优先被打印,优先级相同的成员按照它们在源文件中出现的顺序打印。

@ToString
public class ToStringExample {

  // 静态属性不会包含
  private static final int STATIC_VAR = 10;
  private String name;
  private String[] tags;
  private Shape shape = new Square(5, 10);

  // 排除指定字段不会被 toString 打印
  @ToString.Exclude
  private int id;

  public String getName() {
    return this.name;
  }

  // callSuper 表示是否扩展父类的 toString(), 
  // includeFieldNames 表示是否包含属性名称
  @ToString(callSuper = true, includeFieldNames = true)
  public static class Square extends Shape{
    private final int width, height;

    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }

  public static class Shape {}
}

测试一下上面的示例

ToStringExample toStringExample = new ToStringExample();
System.out.println(toStringExample);

输出如下

ToStringExample(name=null, tags=null, shape=ToStringExample.Square(super=com.project.lombok.ToStringExample$Square@1b9e1916, width=5, height=10))
  • 注释掉 callSuper = true, 测试结果如下
ToStringExample(name=null, tags=null, shape=ToStringExample.Square(width=5, height=10))

从输出可以看出,如果不扩展父类,不会输出关于 Shape 的内部类信息,callSuper 默认为 false

  • 注释掉 includeFieldNames,测试结果不会发生变化,所以 includeFieldNames 默认值为 true
  • 更改 includeFieldNames = false,测试结果如下
ToStringExample(name=null, tags=null, shape=ToStringExample.Square(super=com.project.lombok.ToStringExample$Square@1b9e1916, 5, 10))

从输出可以看出,如果设置 includeFieldNames = false ,不会输出Shape 中的字段名称信息。

上面用@ToString 注解修饰的例子就相当于是下面这段代码

import java.util.Arrays;

public class ToStringExample {
  private static final int STATIC_VAR = 10;
  private String name;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;

  public String getName() {
    return this.getName();
  }

  public static class Square extends Shape {
    private final int width, height;

    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }

    @Override public String toString() {
      return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
    }
  }

  @Override public String toString() {
    return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")";
  }

  public static class Shape {}
}

@EqualsAndHashCode

任何类的定义都可以用@EqualsAndHashCode 标注,让 lombok 为其生成 equalshashCode 方法。默认情况下,将会用在非静态,非 transient 标记的字段上,但是你可以通过 @EqualsAndHashCode.Include@EqualsAndHashCode.Exclude 标记类型成员来修改使用哪些字段。或者,你可以通过使用 @EqualsAndHashCode.Include 并使用 @EqualsAndHashCode(onlyExplicitlyIncluded = true)标记它们来准确指定你希望使用的字段或方法。

如果将 @EqualsAndHashCode 应用于扩展另一个的类,这个特性就会变的很危险。通常来说,对类自动生成equalshashcode 方法不是一个好的选择,因为超类也定义了字段,这些字段也需要equals / hashCode方法。通过设置 callSuper 为 true,可以在生成的方法中包含超类的 equals 和 hachcode 方法。对于 hashCode 来说,super.hashCode 的结果包括了哈希算法,对于 equals 来说,如果超类实现认为它不等于传入的对象,生成的方法将返回 false。请注意,不是所有的equals 实现都能正确处理这种情况。然而,lombok生成的 equals实现可以正确处理这种情况。

如果不扩展类时(只扩展任何java.lang.Object 类)时把 callSuper 设置为 true 会提示编译错误,因为 lombok 会将生成的 equals() 方法和 hashCode() 实现转换为从 Object 继承过来:只有相同的 Object 对象彼此相等并且具有相同的 hashCode 。当你继承其他类时没有设置 callSuper 为 true 会进行警告,因为除非父类没有相同的属性,lombok无法为您生成考虑超类声明的字段的实现。你需要自己写实现类或者依赖 callSuper 工具。你还可以使用 lombok.equalsAndHashCode.callSuper 配置key。

下面是一个例子

@EqualsAndHashCode
public class EqualsAndHashCodeExample {

    private transient int transientVar = 10;
    private String name;
    private double score;
    @EqualsAndHashCode.Exclude private Shape shape = new Square(5,10);
    private String[] tags;
    @EqualsAndHashCode.Exclude private int id;

    public String getName() {
        return name;
    }

    @EqualsAndHashCode(callSuper = true)
    public static class Square extends Shape {
        private final int width,height;

        public Square(int width,int height){
            this.width = width;
            this.height = height;
        }
    }

    public static class Shape {}
}

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

lombok 有三个生成构造函数的注解,下面一起来看一下它们的使用说明和示例

@NoArgsConstructor 将会生成无参数的构造函数,如果有final 修饰的字段并且没有为 final 修饰的字段进行初始化的话,那么单纯的使用 @NoArgsConstructor 注解会提示编译错误

修改建议:需要为 @NoArgsConstructor 指定一个属性@NoArgsConstructor(force=true),lombok会为上面的final 字段默认添加初始值,因为id 是 int 类型,所以 id 的初始值为 0,类似的不同类型的字段的初始值还有 false / null / 0,特定的 Java 构造,像是 hibernate 和 服务提供接口需要无参数的构造方法。此注解主要与 @Data 或生成注解的其他构造函数组合使用。

这里有个需要注意的地方:@NonNull 不要和 @NoArgsConstructor 一起使用

@NoArgsConstructor
@Getter
public class NoArgsConstructorExample {
    private  Long id ;
    private @NonNull String name;
    private Integer age;

    public static void main(String[] args) {
        System.out.println(new NoArgsConstructorExample().getName());
    }
}

输出结果是 null ,因此如果有 @NonNull 修饰的成员的变量就不要用 @NoArgsConstructor 修饰类

@RequiredArgsConstructor 将为每个需要特殊处理的字段生成一个带有1个参数的构造函数。所有未初始化的 final 字段都会获取一个参数,以及标记为 @NonNull 的任何字段也会获取一个参数。这些字段在声明它们的地方没有初始化。对于这些标记为 @NonNull 的字段,会生成特殊的null 编译检查。如果标记为 @NonNull 的字段的参数为 null,那么构造函数将会抛出 NullPointerException。参数的顺序与字段在类中的显示顺序相匹配。

例如下面这个例子,只有 @NonNull 和 final 修饰的字段才会加入构造函数

@RequiredArgsConstructor
public class RequiredArgsConstructorExample {

    @NonNull
    private int id;
    private final String name;
    private boolean human;

}

生成的结果大概是这样的

public class RequiredArgsConstructorExample {
    @NonNull
    private int id;
    private final String name;
    private boolean human;

    public RequiredArgsConstructorExample(@NonNull int id, String name) {
        if (id == null) {
            throw new NullPointerException("id is marked @NonNull but is null");
        } else {
            this.id = id;
            this.name = name;
        }
    }
}

@AllArgsConstructor: @AllArgsConstructor 为类中的每个字段生成一个带有1个参数的构造函数。标有@NonNull 的字段会导致对这些参数进行空检查。

@AllArgsConstructor
public class AllArgsConstructorExample {

    private int id;
    private String name;
    private int age;

}

相当于自动生成如下代码

public AllArgsConstructorExample(int id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
}

这些注解中的每一个都允许使用替代形式,其中生成的构造函数始终是私有的,并且生成包含私有构造函数的附加静态工厂方法,通过为注释提供staticName值来启用此模式,@RequiredArgsConstructor(staticName =“of”)。看下面这个例子

@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
  private int x, y;
  @NonNull private T description;

  @NoArgsConstructor
  public static class NoArgsExample {
    @NonNull private String field;
  }
}

就会变为

public class ConstructorExample<T> {
  private int x, y;
  @NonNull private T description;

  private ConstructorExample(T description) {
    if (description == null) throw new NullPointerException("description");
    this.description = description;
  }

  public static <T> ConstructorExample<T> of(T description) {
    return new ConstructorExample<T>(description);
  }

  @java.beans.ConstructorProperties({"x", "y", "description"})
  protected ConstructorExample(int x, int y, T description) {
    if (description == null) throw new NullPointerException("description");
    this.x = x;
    this.y = y;
    this.description = description;
  }

  public static class NoArgsExample {
    @NonNull private String field;

    public NoArgsExample() {
    }
  }
}

本文分享自微信公众号 - Java建设者(javajianshe)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏solate 杂货铺

etcd v2文档(3) -- 库和工具

14930
来自专栏Java栈

Java获取本地计算机基本信息

public static List<String> getIpAddress(){

22610
来自专栏Hadoop实操

SQL on Hadoop在快手大数据平台的实践与优化

SQL on Hadoop,顾名思义它是基于Hadoop生态的一个SQL引擎架构,我们其实常常听到Hive、SparkSQL、Presto、Impala架构,接...

28130
来自专栏服务器安全专线

apache后缀名支持 让apache支持apk ipk下载的方法

一般都在mime.types文件中添加相应的后缀,重启apache后即可mime.types 文件在\conf\目录里面,添加如下内容:

11700
来自专栏solate 杂货铺

go url 参数编码和解码

当需要将参数解码成url.Values{}类型,也就是 map[string][]string,使用

33120
来自专栏Spark学习技巧

Thrift or gRPC ?Alluxio RPC框架的深度实践总结

作为Alluxio 2.0发布版本的一部分,我们将RPC框架从Apache Thrift(见文末链接1)变为gRPC(见文末链接2)。在本文中,我们将讨论这一变...

35120
来自专栏Java编程技术

谈谈Golang并发编程

Go语言在设计时,Java和C ++是编写服务器程序最常用的语言(至少在Google是这样),这是因为使用这些语言可以高效的开发。但是Go设计者们觉得像Java...

13020
来自专栏java架构1+1

程序员加班2个月做项目,奖励却被夺走,16行代码教经理做人

java现在非常火,语法简单而且功能强大,很多同学都想学java!所以小的给各位看官们准备了收藏已久的视频教程分享给大家!希望能带给大伙儿一些学习上的帮助

7900
来自专栏solate 杂货铺

php init()和__construct() 区别

__construct(): 是PHP内置的构造函数,实例化之前 PHP 解析引擎自动调用,做一些初始化的工作或者外部服务器检测的工作。在实例化对象之前需要做的...

19710
来自专栏Java架构学习路线

11道高频Java面试题解析(Spring+Spring MVC+MyBatis)

最近一直都在面试,整理了几家公司常问的三大框架面试题,现在把它带答案整理好在这里分享给大家,希望对大家有所帮助。

8300

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励