Lombok: 简化样板代码

简介

Java本身是一种非常强大的语言,但是它有一个缺点,就是过于冗余。在很多情况下,我们按照规范实现一个简单的功能却需要非常多的代码,例如许多的getter、setter等。这些代码与业务逻辑无关,属于样板代码,但是完成起来却是枯燥乏味且消耗时间的。而Lombok可以帮助我们简化这些样板代码。

Lombok的目的是帮助程序员简化样板代码的书写,避免编写重复冗余的样板代码。在Lombok中,提供了许多注解,它们各自有不同的功能。在编译阶段,Lombok会根据这些注解来生成对应的样板代码,添加到.class文件当中,从而简化我们的开发过程。

Lombok的引入可以通过Maven来完成,如下所示。注意这里的作用范围指定为provided,这是因为我们只在编写代码以及编译阶段会用到该依赖,在编译完成之后,Lombok就已经完成了它的使命。

1
2
3
4
5
6
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>

常用注解

Lombok的使用主要就是注解的使用,不同的注解对应不同的简化功能。完整的注解功能可以参考官方文档:projectlombok|features,这里主要结合案例来介绍一些常用的注解。

Getter&Setter

首先是@Getter@Setter注解。该注解辅助生成对应的Getter和Setter。默认情况下生成的是public方法,如果需要指定其他作用范围,则需要在注解的Value值中进行指定,可选值有PUBLICPROTECTEDPACKAGEPRIVATE等,在AccessLevel的实现当中。同时需要注意的是,在生成的方法名称中,布尔类型对应的Getter方法名称为isXXX

使用Lombok的实现:

1
2
3
4
5
6
7
8
9
@Getter
@Setter
public class Worker {
int id;
@Setter(AccessLevel.PROTECTED)
String name;
boolean active;
}

等价Java原始实现:

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
public class Worker {
int id;
String name;
boolean active;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

public boolean isActive() {
return active;
}

public void setActive(boolean active) {
this.active = active;
}
}

Constructor

构造器相关的注解包括@NoArgsConstructor@RequiredArgsConstructor@AllArgsConstructor,对应不同参数构造器。

无参构造器以及完整参数构造器容易理解,其中@RequiredArgsConstructor注解处理的则是那些一定需要初始化的字段,例如使用final修饰的字段,使用@NonNull修饰的字段等。不过该注解不会处理静态属性字段,

ToString

@ToString注解辅助完成toString方法的生成。

默认情况下返回的是一个包含类名的字符串,后面跟上每个字段的的名称以及值,使用逗号分隔,类似Worker(id=0, name=null, active=false)。可以使用includeFieldNames=false来排除字段名,类似Worker(0, null, false)

默认情况下返回字符串会考虑所有非静态字段。可以使用@ToString.Exclude修饰字段来手动忽略。或者是指定@ToString(onlyExplicitlyIncluded = true),然后利用@ToString.Include来显示指定需要包含的字段。

使用Lombok的实现:

1
2
3
4
5
6
@ToString
public class Worker {
private int id;
private String name;
private boolean active;
}

等价Java原始实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Worker {
private int id;
private String name;
private boolean active;

@Override
public String toString() {
return "Worker(" +
"id=" + id +
", name=" + name +
", active=" + active +
')';
}
}

EqualsAndHashCode

@EqualsAndHashCode注解辅助生成equals()方法以及hashCode()方法,默认考虑所有非static以及所有非transient字段,不过也可以参考@ToString中的字段Include以及Exclude方法来进行手动调整。

Data

@Data注解的功能类似于上面一系列注解的集合。该注解可以帮助我们直接创建出一个POJO对象,也就是说其中会包括所有字段的Getter、所有非final字段的Setter、涉及类中每个字段的toString、equeals以及hashCode实现,以及所有final字段的构造函数。

使用Lombok的实现:

1
2
3
4
5
6
@Data
public class Worker {
int id;
String name;
boolean active;
}

Value

@Value类似于@Data注解,它会生成相关的方法,但是不包括Setter方法;此外它会将所有属性都看作不可变的final类型,同时使用private进行修饰;同时类本身也会被标注为final。

使用Lombok的实现:

1
2
3
4
5
6
@Value
public class Worker {
int id;
String name;
boolean active;
}

NonNull

@NonNull可以帮助生成对应的空检查语句。需要注意的是,该注解只能出现在可能为null的类型上,例如String,而不能修饰Primitive type,例如int、boolean上,为了解决这个问题,我们可以使用包装类代替原始类型。

使用Lombok的实现:

1
2
3
4
5
6
7
8
9
10
11
public class Worker {
private int id;
private String name;
private boolean active;

public Worker(int id, @NonNull String name, boolean active) {
this.id = id;
this.name = name;
this.active = active;
}
}

等价Java原始实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Worker {
private int id;
private String name;
private boolean active;

public Worker(int id, String name, boolean active) {
this.id = id;
if(name == null) {
throw new NullPointerException("name is marked non-null but is null");
}
this.name = name;
this.active = active;
}
}

高级注解

Cleanup

@Cleanup注解能够确保资源的关闭方法被调用。默认情况下,资源的关闭方法为close(),不过也可以在注解中进行指定。该注解背后使用的是try-with-resource表达方式来完成的。

Synchronized

@Synchronized可以帮助我们生成类似synchronized功能的代码。默认情况下普通方法对$lock对象加锁、静态方法对$LOCK对象加锁。当然我们也可以在注解Value中可以指定需要加锁的对象。

使用Lombok的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SynchronizedDemo {
private final Object objectToLock = new Object();

@Synchronized
public static void sayHello() {
System.out.println("Hello!");
}

@Synchronized
public int getOne() {
return 1;
}

@Synchronized("objectToLock")
public void printObject() {
System.out.println(objectToLock);
}
}

等价Java原始实现:

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 SynchronizedDemo {
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();

public static void sayHello() {
synchronized($LOCK) {
System.out.println("Hello");
}
}

public int getOne() {
synchronized($lock) {
return 1;
}
}

public void printObject() {
synchronized(readLock) {
System.out.println(objectToLock);
}
}
}

SneakyThrows

对于一个方法来说,如果它会抛出异常,那么它要么需要在方法内部使用try-catch进行异常处理,要么需要在方法后面声明异常抛出,否则将无法通过编译。而@SneakyThrows会抛出所有受检异常,我们只需要使用该注解,而不需要使用上面的解决方案。

使用Lombok的实现:

1
2
3
4
@SneakyThrows
public void throwException() {
throw new Throwable();
}

等价Java原始实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void throwException() throws Throwable {
throw new Throwable();
}

// or

public void throwException() {
try {
throw new Throwable();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}

Builder

@Builder可以帮助我们方便的实现一个build模式的对象,添加该注解之后,我们就可以使用相关的构建方法来完成对象的构建。

使用Lombok的实现:

1
2
3
4
5
6
@Builder
public class Worker {
int id;
String name;
boolean active;
}

添加注解之后,Lombok会辅助生成一个Builder内部类,以及多个相关方法,包括builder()build()等,之后可以使用如下方法来构建对象。

1
2
Worker.WorkerBuilder builder = Worker.builder();
Worker worker = Worker.builder().id(1).name("name").active(true).build();

Log

绝大多数的日志框架在使用之前都需要我们在类中声明一个static final的日志记录器类Logger。使用@Log能够帮助我们简化这一声明,Lombok会自动帮助创建一个日志记录器对象,名称为log。在lombok.extern中提供了多种不同的日志注解,用于适配不同的日志框架,我们可以根据实际情况来使用不同的注解。

参考文章

  1. A Complete Guide to Lombok
  2. Introduction to Project Lombok | Baeldung

Lombok: 简化样板代码
http://example.com/2023/07/01/Lombok-简化样板代码/
作者
EverNorif
发布于
2023年7月1日
许可协议