先说结论吧,能不用尽量不用,如果已经用了,那就用好。

会用不等于用的对,所以你有必要了解一下到底什么是Lombok,而不单单只是感觉我写个注解,帮我省去了好多代码,好棒,笑cry。

在示例代码部分,我直接从Lombok的官网复制过来的,而且并没有对应的DeLombok代码,如果你想知道,最好的方法就是,在你的电脑上编译一遍我给的代码,然后反编译查看源码,这样能加深你对Lombok的理解。说白了,就是我懒。

什么是Lombok?

看官方的一段介绍吧:

Project Lombok makes java a spicier language by adding 'handlers' that know how to build and compile simple, boilerplate-free, not-quite-java code.

意思就是通过增加一些处理器,使得Java可以构建和编译更简洁、无样本文件、不完全是Java的代码。

而这些处理程序是通过注解的形式使用的,只要你在相应的Bean上添加了注解,就会有相应的处理程序在编译代码时,对代码做对应修改,这样生成的字节码文件中就会有我们需要的内容。例如我们常见的POJO,私有属性一般通过getter/setter方法进行访问。如果修改/删除/添加属性,还需要同步更新对应的getter/setter方法,如果忘记了,就很容易出问题。在类上使用@Getter和@Setter注解后,b编译生成的字节码文件会为所有属性添加对应的getter/setter方法。如此一来,极大的简化了Bean的维护难度。

如何使用Lombok?

首先第一步需要引入对应的依赖。

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

一般我们使用IDEA作为集成开发环境还需要安装对应的插件,这样在调用使用Lombok注解生成的getter/setter等方法时才不会报错。现在较新版本的IDEA一般自带这个插件,如果你发现报错的话,可以自己检查一下是不是有这个插件在,如果不在或者时禁用的状态,安装或者重新启用一下试试。

Lombok有哪些注解?

Lombok的引入非常的简单,但是Lombok的注解大部分人只知道@Data,如果你想更好的使用Lombok,最好深入了解一个Lombok有哪些注解,以及这些注解的用途,所谓用途,就是你需要知道这个注解替我们做了哪些事情,如果不使用这些注解,我们的代码需要怎么写。

@Data

Lombok最常用的注解,如果使用在类上,会为这个类的所有属性生成对应的getter/setter方法以及覆写eauqls、hashCode、toString方法,如果为final类型,则不会生成对应的setter方法。

示例如下:

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

@Getter/@Setter

由于@Data过于暴力,一般我只需要getter/setter方法就可以了,我是不会直接使用@Data的,需要什么就使用对应的注解,这样可以做到精细化的访问控制。

@Getter/@Setter可以使用在类上,可以为所有属性生成对应的getter/setter方法,final类型的属性不会生成setter方法。同时,@Getter/@Setter也可以使用在具体的某个属性上,为某个或几个属性生成getter/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);
    }
}

@NonNull

这个注解可以使用在属性或者是构造器上,Lombok会生成一个非空的声明,可以用于校验参数,防止空指针异常。

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

@Cleanup

这个注解使用的比较少,可以说我基本上没用过。这个注解的作用是能够帮助我们自动调用close方法,在一些资源需要释放的场景下,会很方便。前提是被注解的资源需要实现CloseAble接口。

示例如下:

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

@EqualsAndHashCode

这个注解会帮助我们生成equals和hashCode方法,这两个方法常和集合框架配合在一起使用,使用不当会极大影响性能,例如Map中本来线性级的查找,将会变成指数极的查找。

默认情况下,@EqualsAndHashCode注解会使用所有非静态(非static)的和非瞬态(非transient)的属性,生成equals和hashCode方法。我们也可以使用注解的exclude参数,排除某一些属性。

示例如下:

@EqualsAndHashCode(exclude={"id", "shape"})
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;
    }
    @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;
        }
    }
}

@ToString

在类上使用这个注解,会帮助我们覆写toString方法,默认会输出类名,所有属性值,并且按照逗号分隔。通常我们使用注解的参数includeFieldName属性,将之设置为true,这样输出的string中会包含属性名称,会更加易于阅读。

示例如下:

@ToString(exclude="id")
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();
    }

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

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

这三个注解放在一起,都是生成构造器的,分别是无参构造器,部分参数构造器,全参构造器。很遗憾,Lombok并不能实现不同参数构造器的重载,只能生成三种,可能原因在于@RequiredArgsConstructor没有使用@Repeatable元注解,所以只能使用一次。@Repeatable是Java8新增的一个元注解,我想Lombok应该是为了兼容性考虑吧。

示例如下:

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

Lombok原理剖析

开头提到了,Lombok会增加一个些处理器,在编译的时候,在生成的字节码文件中添加对应的方法。如果你了解注解,应该知道,注解本身并不改变程序的任何结构,对代码的处理是注解对应的处理程序所做的工作。

Java的注解有两种解析模式,一种是运行期进行解析和处理,另一种是编译器进行解析和处理。显而易见的,Lombok是在编译期解析的。编译期解析有两种机制,一个是Annotation Processing Tool,另一个是Pluggable Annotation Processing API,前者子JDK1.6开始就被标记为废弃,可以用后者代替。有兴趣的读者可以自行深入了解一下。

LomBok本质上就是一个实现了JSR 269 API的程序,在使用javac编译的时候,它做了下面这些事情:

  1. javac对源代码进行分析,生成了一棵抽象语法树(AST);
  2. 运行过程中调用实现了“JSR 269 API”的Lombok程序;
  3. 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点;
  4. javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)。

该不该使用Lombok?

我觉得这是个见仁见智的问题,罗卜青菜各有所爱,支持的人说,使用Lombok可以使得代码看起来更简洁,减少代码量;反对的人说,Lombok改变了Java的语法(所以要依赖于插件),降低了源码的可读性。要我说,一个工具而已,如果项目中有人在用了,你也跟着用就是了。与其花时间在这些没有意义的争论上,不如多花点时间,重构一下那坨看了一遍就不想看第二遍的狗屎代码。说的我都笑了。

最后修改:2021 年 08 月 03 日
如果觉得我的文章对你有用,请随意赞赏