Lombok笔记

作用

以 Java 注解的方式,减少 Java 中的样板代码,如 set/get 方法

安装

  1. 在 IDE 中安装 lombok 插件,目前常用的 Java IDE 都支持 Lombok 插件,如 IntelliJ IDEA、Eclipse、Netbeans、vs code 等。因为 Lombok 需要在代码编译时,根据相应的注解,生成相应的方法(如 根据 @Getter 注解生成 getter() 方法),而 IDE 需要使用 lombok 插件来提供对 lombok 的支持。
  2. 需要引入 lombok 的依赖,因为要在代码中使用注解。

maven 方式引入 lombok 依赖:

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.16</version>
	<scope>provided</scope>
</dependency>

常用注解

@NonNull

在lombok v0.11.10中引入。

作用: 生成对参数的空指针检查语句。

使用位置: 可以用在方法构造函数参数上。

通过 lombok 生成的方法或构造函数(例如,通过 @Data 注解),都会为参数加上 @NonNull 注解,即为参数添加空指针检查。

@NonNull 生成的代码实例:

if (param == null) throw new NullPointerException("参数名称 is marked @NonNull but is null");

生成的空指针检查,会放到普通方法体的最顶端,放在构造函数的 this()/super() 方法下面

如果顶部已经存在空检查,则不会生成其他空检查。

使用 lombok 的代码:

 import lombok.NonNull;

public class NonNullExample extends Something {
  private String name;
  
  public NonNullExample(@NonNull Person person) {
    super("Hello");
    this.name = person.getName();
  }
}

对应的 java 代码:

 import lombok.NonNull;

public class NonNullExample extends Something {
  private String name;
  
  public NonNullExample(@NonNull Person person) {
    super("Hello");
    if (person == null) {
      throw new NullPointerException("person is marked @NonNull but is null");
    }
    this.name = person.getName();
  }
}

@Getter/@Setter

作用: 生成默认的 getter/setter 方法。

使用位置:

  • 放在属性上,为这个属性生成 getter/setter 方法
  • 放在上,为这个类中的所有非静态(static)属性生成 getter/setter 方法

对于字段 foo,生成的 getter/setter 方法名:

  • 生成的 getter 方法,如果 fooboolean,则名称为 isFoo,其他类型生成的方法名为 getFoo
  • 生成的 setter 方法,名称为 getFoo

getter/setter 方法的可见性:

  • 默认为 public
  • 可以在注解中使用 AccessLevel 来控制,合法的可见级别包括: PUBLICPROTECTEDPACKAGE,和 PRIVATE
  • 可以在属性上使用 AccessLevel.NONE 来手动禁用 getter/setter 方法的生成,即覆盖在类上使用的 @Getter@Setter@Data 的行为

使用 lombok 的代码:

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

public class GetterSetterExample {
  
  @Getter @Setter private int age = 10;
  
  @Setter(AccessLevel.PROTECTED) private String name;
  
  @Override public String toString() {
    return String.format("%s (age: %d)", name, age);
  }
}

对应的 java 代码:

 public class GetterSetterExample {
  
  private int age = 10;

  private String name;
  
  @Override public String toString() {
    return String.format("%s (age: %d)", name, age);
  }
  
  public int getAge() {
    return age;
  }
  
  public void setAge(int age) {
    this.age = age;
  }
  
  protected void setName(String name) {
    this.name = name;
  }
}

lombok v1.12.0: 开始支持一些 javadoc 相关的注释,可以查看官方文档

@ToString

作用: 生成 toString()方法。默认按顺序打印类名以及逗号分隔的属性值。(不包含属性名)

使用位置: 类上

@ToString 中设置 includeFieldNames=true 可以在打印结果中包含属性名。

控制打印哪些属性:

  • 默认打印所有属性(非static、非final)
  • 可以在字段上使用 @ToString.Exclude ,来排除属性,即不打印该属性
  • 也可以配合使用:在类上,@ToString(onlyExplicitlyIncluded = true),同时在字段上使用 @ToString.Include ,来精确控制打印哪些属性。
  • @ToString 中设置 callSuper=true 可以调用出父类的 toString 方法。Object 类的 toString 方法意义不大,所以在继承其他类时在使用。
  • 在非静态无参方法上使用 @ToString.Include,可以打印该方法的执行结果

控制属性打印顺序:

  • 使用 @ToString.Include(rank = -1) 为参数设置等级,等级高的属性首先打印
  • 默认等级为 0
  • 等级相同的属性,按照在源文件中出现的顺序打印

使用 lombok 的代码:

import lombok.ToString;

@ToString
public class ToStringExample {
  private static final int STATIC_VAR = 10;
  private String name;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  @ToString.Exclude private int id;
  
  public String getName() {
    return this.name;
  }
  
  @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;
    }
  }
}

对应的 java 代码:

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.name;
  }
  
  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) + ")";
  }
}

@EqualsAndHashCode

作用: 生成 equals(Object other)hashCode() 方法

使用位置: 类上

控制使用哪些属性:

  • 默认使用所有非static、非transient 属性来实现 equals(Object other)hashCode() 方法
  • 可以在字段上使用 @EqualsAndHashCode.Exclude ,来排除属性,即不使用该属性
  • 也可以配合使用:在类上,@EqualsAndHashCode(onlyExplicitlyIncluded = true),同时在字段上使用 @EqualsAndHashCode.Include ,来精确控制使用哪些属性。
  • @EqualsAndHashCode 中设置 callSuper=true 可以将父类的 equals/hashCode 方法应用到生成的自己的 equals/hashCode 方法中。但是要小心使用,如果父类是 Object,那么如果设置了 callSuper=true ,对象可能除了与自己相等,与其他对象都不相等

使用 lombok 的代码:

 import lombok.EqualsAndHashCode;

@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 this.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;
    }
  }
}

对应的 java 代码:

 import java.util.Arrays;

public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public String getName() {
    return this.name;
  }
  
  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof EqualsAndHashCodeExample)) return false;
    EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (Double.compare(this.score, other.score) != 0) return false;
    if (!Arrays.deepEquals(this.tags, other.tags)) return false;
    return true;
  }
  
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.score);
    result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.tags);
    return result;
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof EqualsAndHashCodeExample;
  }
  
  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 boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Square)) return false;
      Square other = (Square) o;
      if (!other.canEqual((Object)this)) return false;
      if (!super.equals(o)) return false;
      if (this.width != other.width) return false;
      if (this.height != other.height) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + super.hashCode();
      result = (result*PRIME) + this.width;
      result = (result*PRIME) + this.height;
      return result;
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Square;
    }
  }
}

还有一些更高级的特性,参考官方文档

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

作用: 生成构造函数

使用位置: 类上

@NoArgsConstructor

  • 生成无参构造函数
  • 如果不能生成无参构造函数(因为 final 属性),会产生编译错误,除非使用 @NoArgsConstructor(force = true) ,使 final 属性初始化为 0 / false / null
  • 对于具有约束的字段(例如@NonNull字段),不会生成检查,因此请注意,通常在这些字段稍后进行正确初始化之前,通常不会满足这些约束。
  • 某些 Java 结构,需要无参构造函数(例如 hibernate 相关类或服务提供接口等),这个注解通常与 @Data 或其他构造函数注解一块儿使用。

@RequiredArgsConstructor

  • 生成一个带有需要特殊处理的属性为参数的构造函数。
    • 标记为 @NonNull 的未初始化的属性,会包含在构造函数的参数中,构造函数中会有对参数非空的检查
    • 为初始化的 final 属性,会包含在构造函数中,并且还会生成一个只包含 final 属性为参数的构造函数
  • 参数的顺序属性在源文件中出现的顺序

@AllArgsConstructor

  • 生成包含所有属性为参数的构造函数,不管属性有没有初始化
  • 如果属性标记为 @NonNull ,会生成非空检查语句

生成另一种构造方式:

  • 原理:生成私有构造方法,以及使用私有构造方法的工具方法
  • 开启:设置注解的 staticName 属性,如 @RequiredArgsConstructor(staticName="of")
  • 此时构造对象,使用这种方式: MapEntry.of("foo", 5) ,而不是传统的 new MapEntry<String, Integer>("foo", 5) 方式
  • 三个构造函数注解,都支持这个属性

更多高级用法,参考官方文档

使用 lombok 的代码:

 import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;

@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;
  }
}

对应的 java 代码:

 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() {
    }
  }
}

@Data

作用: 集合了@ToString@EqualsAndHashCode,所有属性上设置 @Getter,所有非 final 属性上设置了 @Setter@RequiredArgsConstructor

使用位置: 类上

@Data 可以认为是集合多个注解的快捷方式,但是,它缺少一些细粒度的配置,如 callSuper, includeFieldNames and exclude等。

不过,@Data 可以与它集合的那些注解同时使用,来进行细粒度的配置

更多细节参考官方文档

使用 lombok 的代码:

 import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
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;
  }
}

对应的 java 代码:

 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;
    }
  }
}

delombok

将使用 lombok 注解的源码文件,转换为拥有注解对应方法的源码文件。

参考官方文档

是否要使用 lombok

为什么有的程序员不推荐使用Lombok! 这篇文章中讲了一些 lombok 的优缺点,主要包括:

优点:减少样板代码,使代码看起来简洁

缺点:

  • 强制安装IDE插件
  • 代码可读性、可调式性降低
  • 有坑(需要了解正在使用的 lombok 注解的特性)
  • 影响升级
  • 影响封装性

使用前还是要做一些评估。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值