Lombok版本:1.18.10。
Lombok是一个能自动插入IDE并进行构建的Java库工具,通过注解的方式来简化我们的编程,提高效率。它是通过修改底层AST语法树的方式来实现的,相对于反射技术在运行期进行修改,Lombok是在编译期进行修改源码,性能损耗较小。
在我们平时写Java Model的时候都是要写getter/setter方法的,Lombok可以通过@Getter和@Setter注解来完成这一步操作,节省了大量时间。Lombok还有一个@Slf4j注解,可以提供快速打印日志的功能。Lombok提供的功能还有很多。下面举一些常用的注解来进行说明。
有些人不建议使用Lombok,说它会污染项目。拿idea来举例,如果一个人使用了Lombok,别的人都得去下载idea的Lombok插件(即使引入了Lombok依赖也不行)。在我看来,这完全取决于项目经理的决定,如果最终决定要用,那所有人都用也没什么不好的,而且idea下载插件也是很方便的。还有就是有些人会在getter/setter方法里面加上业务代码,使用Lombok注解就不能自定义实现了。首先这就不是推荐的做法,当前在getter/setter方法里面加上的业务代码只满足于当前的需求,假如说后期需要更改需求变动这里的话,还需要再更改getter/setter里面的代码,这样会使得Java Model变得越来越重,Model应该要保持它的纯粹性才对。
正如上面所说,使用Lombok不仅需要引入依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
还需要在IDE中下载Lombok插件才行,如下所示:
1 @Getter / @Setter
顾名思义,这两个注解就是用来生成getter / setter方法的,在类上加上这两个注解即可。使用如下:
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Person {
private Long id;
private String name;
private String address;
}
最终生成的class文件如下所示:
public class Person {
private Long id;
private String name;
private String address;
public Person() {
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
public String getAddress() {
return this.address;
}
public void setId(final Long id) {
this.id = id;
}
public void setName(final String name) {
this.name = name;
}
public void setAddress(final String address) {
this.address = address;
}
}
在像一些idea或Eclipse的IDE中可以快捷键生成getter / setter代码,也很方便。但是如果在开发中后期需要变动Java Model的属性类型或者名字的时候(这种情况说实话还是不少的),就需要将原来的getter / setter代码删掉,再重新快捷键生成,相对来说还是麻烦一些。而如果使用Lombok的@Getter / @Setter注解的话,就只需要修改属性本身即可。
2 @ToString
该注解是用来生成toString方法的,使用如下:
import lombok.ToString;
@ToString
public class Person {
private Long id;
private String name;
private String address;
}
最终生成的class文件如下所示:
public class Person {
private Long id;
private String name;
private String address;
public Person() {
}
public String toString() {
return "Person(id=" + this.id + ", name=" + this.name + ", address=" + this.address + ")";
}
}
跟上面一样,IDE中也可以使用快捷键生成toString方法,但是使用Lombok的@ToString注解会更方便一些。
3 @EqualsAndHashCode
该注解是用来生成equals和hashCode方法的,使用如下:
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class Person {
private Long id;
private String name;
private String address;
}
最终生成的class文件如下所示:
public class Person {
private Long id;
private String name;
private String address;
public Person() {
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else {
label47: {
Object this$id = this.id;
Object other$id = other.id;
if (this$id == null) {
if (other$id == null) {
break label47;
}
} else if (this$id.equals(other$id)) {
break label47;
}
return false;
}
Object this$name = this.name;
Object other$name = other.name;
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
Object this$address = this.address;
Object other$address = other.address;
if (this$address == null) {
if (other$address != null) {
return false;
}
} else if (!this$address.equals(other$address)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.id;
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $name = this.name;
result = result * 59 + ($name == null ? 43 : $name.hashCode());
Object $address = this.address;
result = result * 59 + ($address == null ? 43 : $address.hashCode());
return result;
}
}
如果使用HashMap或者HashSet的时候,一定要留意key是Java Model的情况。需不需要根据当前属性来自定义hashCode和equals方法的实现,需要自定义的话就不能使用该注解。
使用该注解还有一点需要留意的是Model继承的情况。如下述的代码所示:
public class Person extends Human {
private Long id;
private String name;
private String address;
}
class Human {
private Integer height;
private Integer weight;
}
Person继承Human,如果我们在比对两个Person对象的时候,需要比对父类的属性值的时候,则必须在@EqualsAndHashCode注解里加上callSuper = true(默认为false,也就是不比较父类的属性值),如下所示:
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
public class Person extends Human {
private Long id;
private String name;
private String address;
}
class Human {
private Integer height;
private Integer weight;
}
最终生成的class文件如下所示:
public class Person extends Human {
private Long id;
private String name;
private String address;
public Person() {
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else if (!super.equals(o)) {
return false;
} else {
label49: {
Object this$id = this.id;
Object other$id = other.id;
if (this$id == null) {
if (other$id == null) {
break label49;
}
} else if (this$id.equals(other$id)) {
break label49;
}
return false;
}
Object this$name = this.name;
Object other$name = other.name;
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
Object this$address = this.address;
Object other$address = other.address;
if (this$address == null) {
if (other$address != null) {
return false;
}
} else if (!this$address.equals(other$address)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = true;
int result = super.hashCode();
Object $id = this.id;
result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $name = this.name;
result = result * 59 + ($name == null ? 43 : $name.hashCode());
Object $address = this.address;
result = result * 59 + ($address == null ? 43 : $address.hashCode());
return result;
}
}
可以看到在第18行和第66行对父类进行了比较。
4 @AllArgsConstructor / @RequiredArgsConstructor / @NoArgsConstructor
这三个注解都是用来生成构造器的,分别是全参构造器、特定参数的构造器和无参构造器。@AllArgsConstructor和@NoArgsConstructor的使用如下:
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private Long id;
private String name;
private String address;
}
最终生成的class文件如下所示:
public class Person {
private Long id;
private String name;
private String address;
public Person(final Long id, final String name, final String address) {
this.id = id;
this.name = name;
this.address = address;
}
public Person() {
}
}
而@RequiredArgsConstructor注解则会为所有未初始化的final属性和@NonNull注解的属性添加构造器,构造器只会生成一个,包含这些参数:
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class Person {
@NonNull
private Long id;
private final String name;
private String address;
}
最终生成的class文件如下所示:
import lombok.NonNull;
public class Person {
@NonNull
private Long id;
private final String name;
private String address;
public Person(@NonNull final Long id, final String name) {
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
} else {
this.id = id;
this.name = name;
}
}
}
5 @Data
该注解整合了@Getter / @Setter、@ToString、@EqualsAndHashCode和@RequiredArgsConstructor这五个注解,在大部分情况下我们都会使用该注解以代替上面的那些注解,除非像上面所说的继承父类需要比较父类的属性的情况下需要显式调用@EqualsAndHashCode(callSuper = true)注解,还有使用includeFieldNames和exclude的情况等等。@Data的使用如下所示:
import lombok.Data;
@Data
public class Person {
private Long id;
private String name;
private String address;
}
最终生成的class文件如下所示:
public class Person {
private Long id;
private String name;
private String address;
public Person() {
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
public String getAddress() {
return this.address;
}
public void setId(final Long id) {
this.id = id;
}
public void setName(final String name) {
this.name = name;
}
public void setAddress(final String address) {
this.address = address;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else {
label47: {
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id == null) {
break label47;
}
} else if (this$id.equals(other$id)) {
break label47;
}
return false;
}
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$address = this.getAddress();
Object other$address = other.getAddress();
if (this$address == null) {
if (other$address != null) {
return false;
}
} else if (!this$address.equals(other$address)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
Object $address = this.getAddress();
result = result * 59 + ($address == null ? 43 : $address.hashCode());
return result;
}
public String toString() {
return "Person(id=" + this.getId() + ", name=" + this.getName() + ", address=" + this.getAddress() + ")";
}
}
6 @Slf4j
该注解是用来快捷生成日志的,使用如下所示:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test {
public static void main(String[] args) {
if (log.isDebugEnabled()) {
log.debug("log test");
}
}
}
运行结果如下:
01:05:29.719 [main] DEBUG com.hys.test.Test - log test
如果不使用该注解,idea的话也可以配置Live Templates,用快捷键也能生成logger。
7 @Builder
该注解可以使用像建造者模式一样的代码来给Java Model的属性来赋值。比如说我们用setter代码的方式来赋值如下所示:
Person person = new Person();
person.setId(1L);
person.setName("Robert Hou");
person.setAddress("Beijing");
相对来说还是比较繁琐的,尤其是需要全属性赋值的时候尤为明显。使用带参构造器方式的代码如下所示:
Person person = new Person(1L, "Robert Hou", "Beijing");
使用带参构造器赋值的弊端在于我们并不知道赋值的属性是哪个,还是得跳到构造器方法中进行查看。idea中可以显示赋值参数的参数名,算是一种优化:
但这也仅限于使用idea,使用别的编译器就没有这种效果,这种时候就可以考虑使用@Builder这个注解来进行参数赋值:
import lombok.Builder;
@Builder
public class Person {
private Long id;
private String name;
private String address;
}
参数赋值代码如下所示:
Person.builder().id(1L).name("Robert Hou").address("Beijing").build();
可以看到赋值的过程很方便:调用一次方法就完成了一次赋值,通过一行代码,不断链式地调用来完成整个赋值过程。而且一眼就能看清楚赋值的内容是什么,便于维护。
8 @NonNull
该注解会对参数做非空检查,如果为空则会抛出一个空指针异常:
public static void main(String[] args) {
test(null);
}
private static void test(@NonNull String str) {
System.out.println(str.length());
}
生成的class代码如下:
private static void test(@NonNull String str) {
if (str == null) {
throw new NullPointerException("str is marked non-null but is null");
} else {
System.out.println(str.length());
}
}
9 @Cleanup
该注解用在变量前面,会自动调用资源的close方法,例如输入流输出流等等。如果有其他关闭的方法,也可以进行指定:
import lombok.Cleanup;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
File file = new File("d:\\read.txt");
@Cleanup InputStream inputStream = new FileInputStream(file);
byte[] b = new byte[65536];
while (true) {
int r = inputStream.read(b);
if (r == -1) {
break;
}
}
}
}
生成的class代码如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
public class Test {
public Test() {
}
public static void main(String[] args) throws IOException {
File file = new File("d:\\read.txt");
FileInputStream inputStream = new FileInputStream(file);
try {
byte[] b = new byte[65536];
int r;
do {
r = inputStream.read(b);
} while(r != -1);
} finally {
if (Collections.singletonList(inputStream).get(0) != null) {
inputStream.close();
}
}
}
}
10 @SneakyThrows
该注解注释在方法上,可以将方法中的代码try-catch住,默认是抛出Throwable,也可以自己指定异常:
@SneakyThrows
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
生成的class代码如下:
public String utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (Throwable var3) {
throw var3;
}
}
11 @Synchronized
这个注解和synchronized关键字的效果相同,但是Lombok可以给静态方法和实例方法自动加上锁资源:
import lombok.Synchronized;
public class Test {
public static void main(String[] args) {
}
@Synchronized
public static void test1() {
System.out.println();
}
@Synchronized
public int test2() {
return 3;
}
}
生成的class代码如下:
public class Test {
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
public Test() {
}
public static void main(String[] args) {
}
public static void test1() {
synchronized($LOCK) {
System.out.println();
}
}
public int test2() {
synchronized(this.$lock) {
return 3;
}
}
}