lombok学习

java,效率,lombok

Posted by Chris on January 19, 2020

1 概述

java代码中,存在很多样板代码,例如类的getter/setter方法、构造器方法、try…catch方法,这些都有特定的规则。于是,偷懒的程序员自然会想到简化这些工作,减少代码的书写量。通过lombok注解,就可以做到这些,下面详细讲述lombok注解的功能。

2 注解原理

lombok的实现原理是在代码编译阶段,识别指定的注解,自动生成字节码文件。

lombok的解析过程如下:

图中,Lombok以一个注解处理器(annotation processor)的角色运行,类似于一个分发器,提供AST给具体的handler。annotation handler需要用户创建,并且通过SPI来注册,实现具体的处理逻辑,修改AST,之后生成的字节码就是修改过的了。

3 注解使用

3.1 使用步骤

要想在idea里面使用lombok,有如下几步。

  1. 引入pom包
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.16.8</version>
     </dependency>
    
  2. 安装插件

  3. 项目中使用

     @Data
     public class Product {
         private int id;
         private String name;
         private String desc;
     }
    

    这里使用@Data注解,编译之后的字节码就会含有getter/setter,、无参构造器、equals/hashCode方法和toString()方法。

可见,使用Lombok注解,可以减少很多样板代码,节省我们很大的工作量。(笔者感叹:减少样板代码,一直是程序员的目标,例如mybatis项目的一个目标也是减少样板代码。)

3.2 注解列表

lombok注解列表如下:

注解或者简写 作用 示例
val 可以使用val来声明一个局部变量,该局部变量会被声明为一个常量,类型根据初始化表达式来推断出来 val personList = new ArrayList();
var 作用与val类似,不过var用来声明一个局部的可变的变量,类型根据初始化表达式来推断出来 var personId = 1;
@NonNull 用于方法参数前或者构造器参数前,会自动生成null校验的代码 public NonNullExample(@NonNull Person person)
@Cleanup 放在局部变量前,会自动关闭该局部变量所引用的资源 @Cleanup InputStream in = new FileInputStream(args[0]);
@Getter/@Setter 放在类属性前或者类上面,用于生成默认的getter/setter方法 @Setter(AccessLevel.PROTECTED) private String name;
@ToString 自动生成toString()方法,默认会打印类名和非静态属性 @ToString public class ToStringExample {}
@EqualsAndHashCode 含有该注解的类会自动生成equals()和hashCode()方法 @EqualsAndHashCode public class EqualsAndHashCodeExample {}
@NoArgsConstructor 自动生成一个无参的构造器。无参构造器在某些框架下是必须存在的
@RequiredArgsConstructor 自动为必需的属性生成一个构造器,必需的属性包括@NonNull注解的属性、final属性
@AllArgsConstructor 自动生成一个含有全部属性的构造器。
@Data @Data注解相当于同时使用@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor
@Value @Data注解的不可变版本,使用该注解的类,会自动生成@ToString,@EqualsAndHashCode,@Getter,@RequiredArgsConstructor。该类的实例是不可变的
@Builder 使用该注解的类会自动生成建造器模式的指定方法,方便使用建造器模式来创建对象 @Builder public class BuilderExample {}
@SneakyThrows 使用该注解的方法,会自动生成try…catch语句来捕获受检异常 @SneakyThrows(UnsupportedEncodingException.class) public Stringutf8ToString(byte[] bytes) {return new String(bytes, "UTF-8");}
@Synchronized 只能放在实例方法或者静态方法上面,用于将方法体内容包围在一个synchronized中
@With 当final属性想要改变时,一种方式是新建一个对象。属性上使用@With注解,可以自动生成创建新对象的代码。 @With private final int age;
@Getter(lazy=true) 自动生成带有lazy逻辑的getter方法,用于final变量。一般当某个变量的计算逻辑很消耗资源时,会lazy计算并且缓存起来,该注解就很有用处。 @Getter(lazy=true) private final double[] cached = expensive();
@Log 自动生成一行private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Slf4j 自动生成一行 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);

3.3 详细使用

下面,我们将选择几个经常使用的注解,详细讲解其使用。

  • @Data注解
    @Data注解相当于同时使用@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor。 使用示例如下:
      @Data
      public class Product {
          private int id;
          private String name;
          private String desc;
      }
    

    生成的字节码反编译之后如下:

      public class Product {
          private int id;
          private String name;
          private String desc;
    
          public Product() {
          }
    
          public int getId() {
              return this.id;
          }
    
          public String getName() {
              return this.name;
          }
    
          public String getDesc() {
              return this.desc;
          }
    
          public void setId(final int id) {
              this.id = id;
          }
    
          public void setName(final String name) {
              this.name = name;
          }
    
          public void setDesc(final String desc) {
              this.desc = desc;
          }
    
          public boolean equals(final Object o) {
              if (o == this) {
                  return true;
              } else if (!(o instanceof Product)) {
                  return false;
              } else {
                  Product other = (Product)o;
                  if (!other.canEqual(this)) {
                      return false;
                  } else if (this.getId() != other.getId()) {
                      return false;
                  } else {
                      Object this$name = this.getName();
                      Object other$name = other.getName();
                      if (this$name == null) {
                          if (other$name != null) {
                              return false;
                          }
                      } else if (!this$name.equals(other$name)) {
                          return false;
                      }
    
                      Object this$desc = this.getDesc();
                      Object other$desc = other.getDesc();
                      if (this$desc == null) {
                          if (other$desc != null) {
                              return false;
                          }
                      } else if (!this$desc.equals(other$desc)) {
                          return false;
                      }
    
                      return true;
                  }
              }
          }
    
          protected boolean canEqual(final Object other) {
              return other instanceof Product;
          }
    
          public int hashCode() {
              int PRIME = true;
              int result = 1;
              int result = result * 59 + this.getId();
              Object $name = this.getName();
              result = result * 59 + ($name == null ? 43 : $name.hashCode());
              Object $desc = this.getDesc();
              result = result * 59 + ($desc == null ? 43 : $desc.hashCode());
              return result;
          }
    
          public String toString() {
              return "Product(id=" + this.getId() + ", name=" + this.getName() + ", desc=" + this.getDesc() + ")";
          }
      }
    

    区别主要是多了无参构造器、getter/setter方法、equals/hashCode方法、canEqual()方法和toString()方法。这些方法都可以通过@Data注解让lombok为我们自动生成。

  • @Data、@Builder、@NoArgsConstructor和@AllArgsConstructor一起使用
    一般而言,使用@Data注解就可以解决大部分问题了。但是,对于有很多属性的类,在创建对象时,使用getter/setter方法很臃肿。因此,lombok提供了@Builder注解,通过建造器模式来创建对象。
    使用示例
      @Data
      @Builder
      // 若只是使用@Data和@Builder注解,则构造函数只有含全部属性的构造器,缺少无参构造器。为了同时含有无参构造器和全部属性的构造器,则得同时加上以下两个注解。
      @NoArgsConstructor
      @AllArgsConstructor
      public class Product {
          private int id;
          private String name;
          private String desc;
      }
    

    自动生成的代码会添加无参构造器、含全部属性的构造器、getter/setter方法、equals/hashCode方法、canEqual()方法和toString()方法,以及一个ProductBuilder内部类用来建造Product。
    使用的时候,用户可以方便的创建Product了,示例如下:

      private static void builderDemo() {
          Product product = new Product.ProductBuilder().name("phone").desc("a phone").build();
      }
    

4 总结

这里主要是讲了lombok的基本实现原理和基本使用,已经适合于工作中的绝大部分场景了。想要自己实现一个lombok注解,可以参考Implementing a Custom Lombok Annotation

5 参考

Introduction to Project Lombok
Lombok features Project Lombok: Creating Custom Transformations Implementing a Custom Lombok Annotation