Lombok: 代码简洁之道

在日常开发中,我们常常需要重写 getter / setter , toString , equals / hashCode 等,虽然现在 IDE 都支持 generate , 但是字段变更后还需要再修改,最重要的是:重复而繁琐的代码会影响关键代码的可读性。本文就介绍 Lombok ,使用注解来有效的简化代码。

认识 Lombok

Lombok 是一个Java 库,可以通过注解的方式简化代码,如 getter , equals 或日志变量等。

Lombok 非常适用于 pojo 类对象。

工作原理

Lombok 本质上是实现 JSR 269 API 的程序,自 JDK6 后就被 javac 支持,其可以在 javac 时动态修改语法树,产生真实所需字节码文件。

由此可知,在编译后,我们真实所需的代码就会产生,这种工作方式不同与 反射 , Lombok 不会影响程序性能

利用 javap 可以验证 Lombok 的工作原理。使用 jd-GUI 等工具可以查看详细内容。

1
2
3
4
5
6
7
8
9
10
11
12
package tk.gushizone.java.lombok.getter;

import lombok.Getter;

@Getter
public class Item {

private Integer id;

private String name;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Compiled from "Item.java"
public class package tk.gushizone.java.lombok.getter.Item {
public tk.gushizone.lombok.getter.Item();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public java.lang.Integer getId();
Code:
0: aload_0
1: getfield #2 // Field id:Ljava/lang/Integer;
4: areturn

public java.lang.String getName();
Code:
0: aload_0
1: getfield #3 // Field name:Ljava/lang/String;
4: areturn
}

常用注解

常用注解 作用域 说明
@Getter / @Setter 类 \ 字段 在类上,为所有字段生成,在字段上则只单独生成。
@ToString 生成 toString 方法。
@EqualsAndHashCode 生成 hashCodeequals
@NoArgsConstructor 生成无参的构造方法。
@RequiredArgsConstructor 为必需参数生成构造方法,比如 final 和被 @NonNull 注解的字段。
@AllArgsConstructor 生成包含类中所有字段的构造方法。
@Data 包含 : @Getter , @Setter , @RequiredArgsConstructor , @ToString , @EqualsAndHashCode
@Slf4j 生成日志常量 log ,基于 logback 。对应的还有 @Log4j 等注解。

使用 Lombok

必需条件

引用依赖

1
2
3
4
5
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>

Lombok插件

由工作原理可知, Lombok 是是编译后才会产生对应的方法,而这些方法会在开发中(编译前)被使用,这不会被 IDE等 直接支持,需要插件支持。

获取插件 : Lombok官网

通用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package tk.gushizone.java.lombok.common;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Item {

private Integer id;

private String name;

}

@Getter / @Setter

@Getter / @Setter 可以作用于 字段 ,在类上,为所有字段生成,在字段上则只单独生成。

  • 通过 value() 可以控制访问级别,默认为 public
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package tk.gushizone.java.lombok.getterandsetter;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

@Setter(AccessLevel.PROTECTED)
public class Item {

@Getter
private Integer id;

@Getter
private String name;

private String password;

}

@ToString

@ToString 作用于 ,自动生成 toString 方法。

  • 使用 exclude 可以指定排除字段。
  • 使用 of 可以指定包含字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package tk.gushizone.java.lombok.tostring;

import lombok.ToString;

@ToString(exclude = "password")
public class Item {

private Integer id;

private String name;

private String password;

}

@EqualsAndHashCode

@EqualsAndHashCode 作用于 ,自动生成 hashCodeequals 。适用于简单的生成。

  • 使用 exclude 可以指定排除字段。
  • 使用 of 可以指定包含字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package tk.gushizone.java.lombok.equalsandhashcode;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(of="id")
public class Item {

private Integer id;

private String name;

private String password;

}

Constructor

Lombok 提供了构造器相关的注解 :

  • @NoArgsConstructor : 无参构造。
  • @AllArgsConstructor : 全参构造。
  • @RequiredArgsConstructor : 必需参数构造。

@RequiredArgsConstructor 会自动为 @NonNullfinal 相关的特殊字段构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package tk.gushizone.java.lombok.constructor;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Item {

@NonNull
private Integer id;

private String name;

private String password;

}

@Data

@Data 作用于 ,包含 : @Getter , @Setter , @RequiredArgsConstructor , @ToString , @EqualsAndHashCode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package tk.gushizone.java.lombok.data;

import lombok.Data;
import lombok.NonNull;

@Data
public class Item {

@NonNull
private Integer id;

private String name;

}
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
85
86
87
88
89
90
91
92
93
94
95
96
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package tk.gushizone.java.lombok.data;

import lombok.NonNull;

public class Item {
@NonNull
private Integer id;
private String name;

public Item(@NonNull Integer id) {
if (id == null) {
throw new NullPointerException("id is marked @NonNull but is null");
} else {
this.id = id;
}
}

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

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

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

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

public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Item)) {
return false;
} else {
Item other = (Item)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;
}

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

return true;
}
}
}

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

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());
return result;
}

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

@Slf4j

@Slf4j 作用于 ,自动生成日志常量 log ,基于 logback 。对应的还有 @Log4j 等注解。

1
2
3
4
5
6
7
8
9
10
11
package tk.gushizone.java.lombok.sl4j;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ItemService {

public static void main(String[] args) {
log.info("需要另外配置logback相关依赖。");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package tk.gushizone.java.lombok.sl4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ItemService {
private static final Logger log = LoggerFactory.getLogger(ItemService.class);

public ItemService() {
}

public static void main(String[] args) {
log.info("需要另外配置sl4j相关依赖。");
}
}

@Builder

@Builder 可以提供构造器模式 API 。

@Builder 一般需要配合 @NoArgsConstructor@AllArgsConstructor 一起使用。

  • @Builder 会提供全参构造,其为构造器模式所需,即默认没有无参构造。

  • 但有时一定需要无参构造器,如:mybatis。

1
2
3
4
5
6
7
8
9
10
11
12
package tk.gushizone.java.lombok.builder;

import lombok.Builder;

@Builder
public class SimpleItem {

private Integer id;

private String name;

}
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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package tk.gushizone.java.lombok.builder;

public class SimpleItem {
private Integer id;
private String name;

SimpleItem(Integer id, String name) {
this.id = id;
this.name = name;
}

public static SimpleItem.SimpleItemBuilder builder() {
return new SimpleItem.SimpleItemBuilder();
}

public static class SimpleItemBuilder {
private Integer id;
private String name;

SimpleItemBuilder() {
}

public SimpleItem.SimpleItemBuilder id(Integer id) {
this.id = id;
return this;
}

public SimpleItem.SimpleItemBuilder name(String name) {
this.name = name;
return this;
}

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

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

当 pojo 继承基类时,@Builder需要作用于全参构造器上,才能构造父类属性。

1
2
3
4
5
6
7
8
9
10
package tk.gushizone.java.lombok.builder;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class BaseItem {

private Integer id;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package tk.gushizone.java.lombok.builder;

import lombok.Builder;

public class Item extends BaseItem {

private String name;

@Builder
public Item(Integer id, String name) {
super(id);
this.name = name;
}
}

@SuperBuilder

显而易见,继承式的 @Builder 使用不够优雅。此时可以尝试 @SuperBulier ,它更优雅,但也具有局限性。

  • 继承体系都需要使用 @SuperBuilder
  • @SuperBuilder@Builder 不兼容。
1
2
3
4
5
6
7
8
9
package tk.gushizone.java.lombok.superbuilder;

import lombok.experimental.SuperBuilder;

@SuperBuilder
public class BaseItem {

private Integer id;
}
1
2
3
4
5
6
7
8
9
10
package tk.gushizone.java.lombok.superbuilder;

import lombok.experimental.SuperBuilder;

@SuperBuilder
public class Item extends BaseItem {

private String name;

}
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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package tk.gushizone.java.lombok.superbuilder;

public class BaseItem {
private Integer id;

protected BaseItem(BaseItem.BaseItemBuilder<?, ?> b) {
this.id = b.id;
}

public static BaseItem.BaseItemBuilder<?, ?> builder() {
return new BaseItem.BaseItemBuilderImpl();
}

private static final class BaseItemBuilderImpl extends BaseItem.BaseItemBuilder<BaseItem, BaseItem.BaseItemBuilderImpl> {
private BaseItemBuilderImpl() {
}

protected BaseItem.BaseItemBuilderImpl self() {
return this;
}

public BaseItem build() {
return new BaseItem(this);
}
}

public abstract static class BaseItemBuilder<C extends BaseItem, B extends BaseItem.BaseItemBuilder<C, B>> {
private Integer id;

public BaseItemBuilder() {
}

protected abstract B self();

public abstract C build();

public B id(Integer id) {
this.id = id;
return this.self();
}

public String toString() {
return "BaseItem.BaseItemBuilder(id=" + this.id + ")";
}
}
}
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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package tk.gushizone.java.lombok.superbuilder;

import tk.gushizone.java.lombok.superbuilder.BaseItem.BaseItemBuilder;

public class Item extends BaseItem {
private String name;

protected Item(Item.ItemBuilder<?, ?> b) {
super(b);
this.name = b.name;
}

public static Item.ItemBuilder<?, ?> builder() {
return new Item.ItemBuilderImpl();
}

private static final class ItemBuilderImpl extends Item.ItemBuilder<Item, Item.ItemBuilderImpl> {
private ItemBuilderImpl() {
}

protected Item.ItemBuilderImpl self() {
return this;
}

public Item build() {
return new Item(this);
}
}

public abstract static class ItemBuilder<C extends Item, B extends Item.ItemBuilder<C, B>> extends BaseItemBuilder<C, B> {
private String name;

public ItemBuilder() {
}

protected abstract B self();

public abstract C build();

public B name(String name) {
this.name = name;
return this.self();
}

public String toString() {
return "Item.ItemBuilder(super=" + super.toString() + ", name=" + this.name + ")";
}
}
}