lombok注解编译原理 (lombok教程全套)

简介

使用Lombok插件后,Java开发人员可以节省出重复构建,诸如hashCode和equals这样的方法以及各种业务对象模型的accessor和ToString等方法的大量时间。对于这些方法,它能够在编译源代码期间自动帮我们生成这些方法,并没有如反射那样降低程序的性能。一些简单的Getter和Setter的确可以借助IDE自动生成,复杂一些注解@Cleanup @Buider @With提供了IDE代码生成不具备的功能,能显著提升开发效率、减少BUG。

maven配置

加入依赖

到2021年8月最新的版本是1.18.20,另外scope是provided,因为lombok是编译时使用的,运行时不需要加载。

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

maven编译配置

lombok是利用编译的时候解析和编辑语法树实现的,所以需要在编译的时候引入配置。maven中需要加入到maven-compiler-plugin里面

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<configuration>
		<source>1.8</source>
		<target>1.8</target>
		<annotationProcessorPaths>
			<path>
				<groupId>org.projectlombok</groupId>
				<artifactId>lombok</artifactId>
				<version>${lombok.version}</version>
			</path>
		</annotationProcessorPaths>
	</configuration>
</plugin>

开发IDE配置

这个就比较简单了,选择相应IDE的插件进行安装就好了,下图以IDEA的Lombok为例。

lombok注解编译原理,lombok代码怎么用

主要的用法

val/var

您可以使用val作为局部变量声明的类型,而不是实际写入类型。 执行此操作时,将从初始化表达式推断出类型。 本地变量也将成为最终变量。 此功能仅适用于局部变量和foreach循环,而不适用于类属性(field)。 初始化表达式是必需的。val实际上是一种“类型”,在lombok包中作为一个真正的类存在。 您必须导入它才能使val工作(或使用lombok.val作为类型)。 在本地变量声明中存在这种类型会触发添加final关键字以及复制初始化表达式的类型,该类型会覆盖’伪’val类型。

val和var的区别在于,var标记的局部变量并不是final的。

一般情况下我们申明类型,总是需要这么写

//  列表
        ArrayList<String> example = new ArrayList<>();
        //  map
        Map<String, Integer> map = new HashMap<>(12);
        //  java对象
        JavaBean bean = new JavaBean();

现在可以通过var进行简化

public class ValExample {
  public String example() {
    val example = new ArrayList<String>();
    example.add("Hello, World!");
    val foo = example.get(0);
    return foo.toLowerCase();
  }
  
  public void example2() {
    val map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (val entry : map.entrySet()) {
      System.out.printf("%d: %s\\n", entry.getKey(), entry.getValue());
    }
  }
}

编译以后的代码是

public class ValExample {
  public String example() {
    final ArrayList<String> example = new ArrayList<String>();
    example.add("Hello, World!");
    final String foo = example.get(0);
    return foo.toLowerCase();
  }
  
  public void example2() {
    final HashMap<Integer, String> map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (final Map.Entry<Integer, String> entry : map.entrySet()) {
      System.out.printf("%d: %s\\n", entry.getKey(), entry.getValue());
    }
  }
}

@Cleanup

自动关闭资源,针对实现了java.io.Closeable接口的对象有效,如:典型的IO流对象。

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
    }
  }
}

//编译后
public class CleanupExample {
  public static void main(String[] args) throws IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }
  }
}

@NonNull

主要作用于成员变量和参数中,标识不能为空,否则抛出空指针异常。变异以后的代*会码**增加Null的检查。

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

@Builder用法

@Builder注释为你的类生成相对略微复杂的构建器API。@Builder可以让你以下面显示的那样调用你的代码,来初始化你的实例对象:

Student.builder()
               .sno( "001" )
               .sname( "admin" )
               .sage( 18 )
               .sphone( "110" )
               .build()


@Builder中使用 @Singular 注释集合

@Builder也可以为集合类型的参数或字段生成一种特殊的方法。 它采用修改列表中一个元素而不是整个列表的方式,可以是增加一个元素,也可以是删除一个元素。

在使用@Singular注释注释一个集合字段(使用@Builder注释类),lombok会将该构建器节点视为一个集合,并生成两个adder方法而不是setter方法。

  • 一个向集合添加单个元素
  • 一个将另一个集合的所有元素添加到集合中

@Getter 和@Setter

作用在类上,生成所有成员变量的getter/setter方法;作用于成员变量上,生成该成员变量的getter/setter方法。生成的方法默认的public的可以,可以通过设置AccessLevel改变,可选

有。其中NONE,表示不生成Get/Set方法

public enum AccessLevel {
    PUBLIC,
    MODULE,
    PROTECTED,
    PACKAGE,
    PRIVATE,
    NONE;

    private AccessLevel() {
    }
}

Getter的lazy属性的意思是延迟生效。使用了getter这个annotation可以在实际使用到cached的时候生成cached,同时,Lombok会自动去管理线程安全的问题,不会存在重复赋值的问题。

public class GetterLazyExample {
  @Getter(lazy=true) private final double[] cached = expensive();

  private double[] expensive() {
    double[] result = new double[1000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.asin(i);
    }
    return result;
  }
}


@ToString

@ToString:作用于类,覆盖默认的toString()方法,可以通过of属性限定显示某些字段,通过exclude属性排除某些字段。除了加在类上面,还可以加在类属性上面。

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

@EqualsAndHashCode

作用于类,覆盖默认的equals和hashCode。不需要的内容可以Exclude掉。

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

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

@NoArgsConstructor:生成无参构造器;

@RequiredArgsConstructor:生成包含final和@NonNull注解的成员变量的构造器;还可以定义一个staticName用于生成静态的方法。

@AllArgsConstructor:生成全参构造器.

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

@Data

等价于上面的@Setter、@Getter、@RequiredArgsConstructor、@ToString、@EqualsAndHashCode

如果需要对Get/Set方法进行调整,可以增加@Getter和@Setter

@Value

跟@Data类似,只是所有的字段是private和final的。

@SneakyThrows

很多情况下我们遇到异常是向上一抛了之

try{
}catch(Exception e){
throw new RuntimeException(e);
}

//原始代码
public class SneakyThrowsExample implements Runnable {
  @SneakyThrows(UnsupportedEncodingException.class)
  public String utf8ToString(byte[] bytes) {
    return new String(bytes, "UTF-8");
  }
  
  @SneakyThrows
  public void run() {
    throw new Throwable();
  }
}
//编译以后的代码
public class SneakyThrowsExample implements Runnable {
  public String utf8ToString(byte[] bytes) {
    try {
      return new String(bytes, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw Lombok.sneakyThrow(e);
    }
  }
  
  public void run() {
    try {
      throw new Throwable();
    } catch (Throwable t) {
      throw Lombok.sneakyThrow(t);
    }
  }
}

@With

@with的作用是克隆原来的对象,新的对象的字段进行更新。

//编译以前的代码
public class WithExample {
  @With(AccessLevel.PROTECTED) @NonNull private final String name;
  @With private final int age;
  
  public WithExample(String name, int age) {
    if (name == null) throw new NullPointerException();
    this.name = name;
    this.age = age;
  }
}

//编译以后的代码
public class WithExample {
  private @NonNull final String name;
  private final int age;

  public WithExample(String name, int age) {
    if (name == null) throw new NullPointerException();
    this.name = name;
    this.age = age;
  }

  protected WithExample withName(@NonNull String name) {
    if (name == null) throw new java.lang.NullPointerException("name");
    return this.name == name ? this : new WithExample(name, age);
  }

  public WithExample withAge(int age) {
    return this.age == age ? this : new WithExample(name, age);
  }
}