先说结论吧,能不用尽量不用,如果已经用了,那就用好。
会用不等于用的对,所以你有必要了解一下到底什么是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编译的时候,它做了下面这些事情:
- javac对源代码进行分析,生成了一棵抽象语法树(AST);
- 运行过程中调用实现了“JSR 269 API”的Lombok程序;
- 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点;
- javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)。
该不该使用Lombok?
我觉得这是个见仁见智的问题,罗卜青菜各有所爱,支持的人说,使用Lombok可以使得代码看起来更简洁,减少代码量;反对的人说,Lombok改变了Java的语法(所以要依赖于插件),降低了源码的可读性。要我说,一个工具而已,如果项目中有人在用了,你也跟着用就是了。与其花时间在这些没有意义的争论上,不如多花点时间,重构一下那坨看了一遍就不想看第二遍的狗屎代码。说的我都笑了。