最近 IDEA 2020 最后一个版本发布了,听说已经内置了 Lombok 插件,SpringBoot 2.1.x 之后的版本也在 Starter 中内置了 Lombok 依赖

Lombok简介

Lombok 是一款 Java 代码功能增强库,在 Github 上已有 9.9 k Star。它会自动集成到你的编辑器和构建工具中,从而使你的 Java 代码更加生动有趣。通过 Lombok 的注解,你可以不用再写 getter、setter、equals 等方法,Lombok 将在编译时为你自动生成。

Lombok安装

首先我们需要在 IDEA 中安装好 Lombok 插件,如果你使用的是最新版 IDEA 2020.3,则 Lombok 插件已经内置,无需安装。

img

之后在项目的 pom.xml 文件中添加 Lombok 依赖就好啦

img

SpringBoot 2.1.x 版本后无需指定 Lombok 版本,因为 SpringBoot 在 spring-boot-dependencies 中已经内置了 Lombok

img

Lombok使用

Lombok 中有很多注解,这些注解使得我们可以更加方便的编写 Java 代码,下面介绍下这些注解的使用。

val

使用 val 注解可以取代任意类型作为局部变量,这样我们就不用写复杂的 ArrayListMap.Entry 类型了,具体例子如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @author https://blog.imzjw.cn
* @date 2020/12/23 20:03
*/
public class LombokDemo {

public static void main(String[] args) {
result();
result2();
}

private static void result() {
// val代替 ArrayList<String> 和 String 类型
val list = new ArrayList<String>();
list.add("GARVEY ZHONG");
val res = list.get(0);
// toLowerCase:转小写
System.out.println(res.toLowerCase());
}

private static void result2() {
// val 代替 Map.Entry<Integer,String> 类型
val map = new HashMap<Integer, String>();
map.put(0, "blog.imzjw.cn");
System.out.println(map.get(0));
}
}

// 打印结果
===========================================================
"C:\Program Files\Java\jdk1.8.0_162\bin\java.exe"...
garvey zhong
blog.imzjw.cn

Process finished with exit code 0
===========================================================

当我们使用了 val 注解后,Lombok 会从局部变量的初始化表达式推断出具体类型,编译后会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LombokDemo {
public LombokDemo() {
}

public static void result() {
ArrayList<String> list = new ArrayList<>();
list.add("GARVEY ZHONG");
String res = list.get(0);
System.out.println(res.toLowerCase());
}

public static void result2() {
HashMap<Integer, String> map = new HashMap<>();
map.put(0, "blog.imzjw.cn");
System.out.println(map.get(0));
}
}

@NonNull

在方法上使用 @NonNull 注解可以做非空判断,如果传入空值的话会直接抛出 NullPointerException (俗称空指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LombokDemo {

private String name;

public static void main(String[] args) {
new LombokDemo("garvey");
// 绝对会抛出NullPointerException异常
new LombokDemo(null);
}

public LombokDemo(@NonNull String name) {
this.name = name;
}
}

编译后会在构造器中添加非空判断,具体代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LombokDemo {

private String name;

public static void main(String[] args) {
new LombokDemo("garvey");
new LombokDemo((String)null);
}

public LombokDemo(@NonNull String name) {
if (name == null) {
throw new NullPointerException("name is marked non-null but is null");
} else {
this.name = name;
}
}
}

@Cleanup

当我们在 Java 中使用资源时,不可避免地需要在使用后关闭资源。使用 @Cleanup 注解可自动关闭资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws IOException {
String str = "garvey";
// 使用输入输出流自动关闭,无需编写 try catch 和调用 close() 方法
@Cleanup ByteArrayInputStream in = new ByteArrayInputStream(str.getBytes("UTF-8"));
@Cleanup ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] b = new byte[1024];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
System.out.println(out.toString("UTF-8"));
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void main(String[] args) throws IOException {
String str = "garvey";
ByteArrayInputStream in = new ByteArrayInputStream(inStr.getBytes("UTF-8"));

try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
byte[] b = new byte[1024];

while (true) {
int r = in.read(b);
if (r == -1) {
System.out.println(out.toString("UTF-8"););
return;
}
out.write(b, 0, r);
}
} finally {
if (Collections.singletonList(out).get(0) != null) {
out.close();
}
}
} finally {
if (Collections.singletonList(in).get(0) != null) {
in.close();
}
}
}

@Getter/@Setter

有了 @Getter 和 @Setter 注解,我们再也不用编写 getter and setter 方法了。

其实写 getter 和 setter 方法也不是麻烦事,因为 IDEA 自动生成 getter and setter 方法

麻烦的是如果类属性的类型和名称改了的话,又要重新生成 getter and setter 方法,这就很头疼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student {
@Getter
@Setter
private String name;
@Getter
@Setter(AccessLevel.PROTECTED)
private Integer age;

public static void main(String[] args) {
Student student = new Student();
student.setName("garvey");
student.setAge(20);
System.out.println("name=" + student.getName() + "\nage=" + student.getAge());
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student {
private String name;
private Integer age;

public Student () {
}

public String getName() {
return this.name;
}

public void setName(final String name) {
this.name = name;
}

public Integer getAge() {
return this.age;
}

protected void setAge(final Integer age) {
this.age = age;
}
}

@ToString

使用 @ToString 注解可以自动生成 toString 方法,默认会包含所有类属性,使用 @ToString.Exclude 注解可以排除属性的生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ToString
public class Student {

@ToString.Exclude
private Long id;
private String name;
private Integer age;

public Student(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

public static void main(String[] args) {
Student student = new Student(1L, "garvey", 20);
// 自动实现toString方法,输出ToStringExample(name=garvey, age=20)
System.out.println(student);
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student {
private Long id;
private String name;
private Integer age;

public Student (Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

public String toString() {
return "Student(name=" + this.name + ", age=" + this.age + ")";
}
}

@EqualsAndHashCode

使用 @EqualsAndHashCode 注解可以自动生成 hashCodeequals 方法,默认包含所有类属性,使用 @EqualsAndHashCode.Exclude可以排除属性的生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Getter
@Setter
@EqualsAndHashCode
public class Student {

private Long id;
@EqualsAndHashCode.Exclude
private String name;

@EqualsAndHashCode.Exclude
private Integer age;

public static void main(String[] args) {
Student student = new Student();
student.setId(1L);
student.setName("garvey");
student.setAge(20);
Student student2 = new Student();
student2.setId(1L);
// equals方法只对比id,so 返回true
System.out.println(student.equals(student2));
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Student {
private Long id;
private String name;
private Integer age;

public Student() {
}

public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Student)) {
return false;
} else {
Student other = (Student)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this$id.equals(other$id)) {
return false;
}

return true;
}
}
}

protected boolean canEqual(final Object other) {
return other instanceof Student;
}

public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
return result;
}
}

@XxConstructor

使用 @XxConstructor 注解可以自动生成构造方法,有 @NoArgsConstructor@RequiredArgsConstructor@AllArgsConstructor 三个注解可以使用。

  • @NoArgsConstructor:生成无参构造函数。
  • @RequiredArgsConstructor:生成包含必须参数的构造函数,使用 @NonNull 注解的类属性为必须参数。
  • @AllArgsConstructor:生成包含所有参数的构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor
public class Student {
@NonNull
private Long id;
private String name;
private Integer age;

public static void main(String[] args) {
// 无参构造器
Student student1 = new Student();
// 全部参数构造器
Student student2 = new Student(1L, "garvey", 20);
// @NonNull注解的必须参数构造器
Student student3 = Student.of(1L);
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Student {
@NonNull
private Long id;
private String name;
private Integer age;

public Student() {
}

private Student(@NonNull final Long id) {
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
} else {
this.id = id;
}
}

public static Student of(@NonNull final Long id) {
return new Student(id);
}

public Student(@NonNull final Long id, final String name, final Integer age) {
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
} else {
this.id = id;
this.name = name;
this.age = age;
}
}
}

@Data

@Data 是一个方便使用的组合注解,是 @ToString@EqualsAndHashCode@Getter@Setter@RequiredArgsConstructor 的组合体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Data
public class Student {
@NonNull
private Long id;
@EqualsAndHashCode.Exclude
private String name;
@EqualsAndHashCode.Exclude
private Integer age;

public static void main(String[] args) {
// @RequiredArgsConstructor已生效
Student student1 = new Student(1L);
// @Getter @Setter已生效
student1.setName("garvey");
student1.setAge(20);
// @ToString已生效
System.out.println(student1);
Student student2 = new Student(1L);
// @EqualsAndHashCode已生效
System.out.println(student1.equals(student2));
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class Student {
@NonNull
private Long id;
private String name;
private Integer age;

public Student(@NonNull final Long id) {
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
} else {
this.id = id;
}
}

@NonNull
public Long getId() {
return this.id;
}

public String getName() {
return this.name;
}

public Integer getAge() {
return this.age;
}

public void setId(@NonNull final Long id) {
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
} else {
this.id = id;
}
}

public void setName(final String name) {
this.name = name;
}

public void setAge(final Integer age) {
this.age = age;
}

public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Student)) {
return false;
} else {
Student other = (Student)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this$id.equals(other$id)) {
return false;
}

return true;
}
}
}

protected boolean canEqual(final Object other) {
return other instanceof Student;
}

public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
return result;
}

public String toString() {
return "Student(id=" + this.getId() + ", name=" + this.getName() + ", age=" + this.getAge() + ")";
}
}

@Value

使用 @Value 注解可以把类声明为不可变的,声明后此类相当于 final 类,无法被继承,其属性也会变成 final 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Value
public class Student {
private Long id;
private String name;
private Integer age;

public static void main(String[] args) {
// 只能使用全参构造器
Student student = new Student(1L,"garvey",20);
// student.setName("garvey") // 没有生成setter方法,会报错
// student.name="garvey" // 字段被设置为final类型,会报错
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class Student {
private final Long id;
private final String name;
private final Integer age;

public static void main(String[] args) {
new Student(1L, "garvey", 20);
}

public Student(final Long id, final String name, final Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

public Long getId() {
return this.id;
}

public String getName() {
return this.name;
}

public Integer getAge() {
return this.age;
}
}

@Builder

使用 @Builder 注解可以通过建造者模式来创建对象,建造者模式加链式调用,创建对象太方便了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Builder
@ToString
public class Student {
private Long id;
private String name;
private Integer age;

public static void main(String[] args) {
Student student = Student.builder()
.id(1L)
.name("garvey")
.age(20)
.build();
System.out.println(student);
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Student {
private Long id;
private String name;
private Integer age;

Student(final Long id, final String name, final Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

public static Student.BuilderExampleBuilder builder() {
return new Student.BuilderExampleBuilder();
}

public String toString() {
return "Student(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";
}

public static class BuilderExampleBuilder {
private Long id;
private String name;
private Integer age;

BuilderExampleBuilder() {
}

public Student.BuilderExampleBuilder id(final Long id) {
this.id = id;
return this;
}

public Student.BuilderExampleBuilder name(final String name) {
this.name = name;
return this;
}

public Student.BuilderExampleBuilder age(final Integer age) {
this.age = age;
return this;
}

public Student build() {
return new Student(this.id, this.name, this.age);
}

public String toString() {
return "Student.BuilderExampleBuilder(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";
}
}
}

@SneakyThrows

还在手动捕获并抛出异常?使用 @SneakyThrows 注解自动实现试试!

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student {

// 自动抛出异常,无需处理
@SneakyThrows(UnsupportedEncodingException.class)
public static byte[] str2byte(String str) {
return str.getBytes("UTF-8");
}

public static void main(String[] args) {
String str = "garvey";
System.out.println(str2byte(str).length);
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
public class Student {
public Student() {
}

public static byte[] str2byte(String str) {
try {
return str.getBytes("UTF-8");
} catch (UnsupportedEncodingException var2) {
throw var2;
}
}
}

@Synchronized

当我们在多个线程中访问同一资源时,往往会出现线程安全问题,以前我们往往使用 synchronized 关键字修饰方法来实现同步访问。使用 @Synchronized 注解同样可以实现同步访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Data
public class SynchronizedExample {
@NonNull
private Integer count;

@Synchronized
@SneakyThrows
public void reduceCount(Integer id) {
if (count > 0) {
Thread.sleep(500);
count--;
System.out.println(String.format("thread-%d count:%d", id, count));
}
}

public static void main(String[] args) {
// 添加@Synchronized三个线程可以同步调用reduceCount方法
SynchronizedExample example = new SynchronizedExample(20);
new ReduceThread(1, example).start();
new ReduceThread(2, example).start();
new ReduceThread(3, example).start();
}


@RequiredArgsConstructor
static class ReduceThread extends Thread {
@NonNull
private Integer id;
@NonNull
private SynchronizedExample example;

@Override
public void run() {
while (example.getCount() > 0) {
example.reduceCount(id);
}
}
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SynchronizedExample {
private final Object $lock = new Object[0];
@NonNull
private Integer count;

public void reduceCount(Integer id) {
try {
synchronized(this.$lock) {
if (this.count > 0) {
Thread.sleep(500L);
Integer var3 = this.count;
Integer var4 = this.count = this.count - 1;
System.out.println(String.format("thread-%d count:%d", id, this.count));
}

}
} catch (Throwable var7) {
throw var7;
}
}
}

@With

使用 @With 注解可以实现对原对象进行克隆,并改变其一个属性,使用时需要指定全参构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@With
@AllArgsConstructor
public class WithExample {
private Long id;
private String name;
private Integer age;

public static void main(String[] args) {
WithExample example1 = new WithExample(1L, "garvey", 20);
WithExample example2 = example1.withAge(22);
// 将原对象进行clone并设置age,返回false
System.out.println(example1.equals(example2));
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WithExample {
private Long id;
private String name;
private Integer age;

public WithExample withId(final Long id) {
return this.id == id ? this : new WithExample(id, this.name, this.age);
}

public WithExample withName(final String name) {
return this.name == name ? this : new WithExample(this.id, name, this.age);
}

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

public WithExample(final Long id, final String name, final Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
}

@Getter(lazy=true)

当我们获取某一个属性比较消耗资源时,可以给 @Getter 添加 lazy=true 属性实现懒加载,会生成 Double Check Lock 样板代码对属性进行懒加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

public static void main(String[] args) {
// 使用Double Check Lock 样板代码对属性进行懒加载
GetterLazyExample example = new GetterLazyExample();
System.out.println(example.getCached().length);
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class GetterLazyExample {
private final AtomicReference<Object> cached = new AtomicReference();

public GetterLazyExample() {
}

private double[] expensive() {
double[] result = new double[1000000];

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

public double[] getCached() {
Object value = this.cached.get();
if (value == null) {
synchronized(this.cached) {
value = this.cached.get();
if (value == null) {
double[] actualValue = this.expensive();
value = actualValue == null ? this.cached : actualValue;
this.cached.set(value);
}
}
}

return (double[])((double[])(value == this.cached ? null : value));
}
}

@Log

使用 @Log 注解,可以直接生成日志对象 log,通过 log 对象可以直接打印日志。

1
2
3
4
5
6
7
8
@Log
public class LogExample {
public static void main(String[] args) {
log.info("level info");
log.warning("level warning");
log.severe("level severe");
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
public class LogExample {
private static final Logger log = Logger.getLogger(LogExample.class.getName());

public LogExample() {
}

public static void main(String[] args) {
log.info("level info");
log.warning("level warning");
log.severe("level severe");
}
}

@Slf4j

使用 Lombok 生成日志对象时,根据使用日志实现的不同,有多种注解可以使用。比如 @Log、@Log4j、@Log4j2、@Slf4j 等。

1
2
3
4
5
6
7
8
@Slf4j
public class LogSlf4jExample {
public static void main(String[] args) {
log.info("level:{}","info");
log.warn("level:{}","warn");
log.error("level:{}", "error");
}
}

编译后 Lombok 会生成如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
public class LogSlf4jExample {
private static final Logger log = LoggerFactory.getLogger(LogSlf4jExample.class);

public LogSlf4jExample() {
}

public static void main(String[] args) {
log.info("level:{}", "info");
log.warn("level:{}", "warn");
log.error("level:{}", "error");
}
}

参考资料