0%

Java代码生成工具之Lombok

Lombok——Java开发常用的代码生成工具。通过使用注解,在编译期将注解替换为相应代码

abstract.jpg

配置

POM依赖如下

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

然后,在IDEA的Plugins Marketplace 中搜索安装Lombok插件。最后,在IDEA中使能注解处理器功能即可

figure 1.jpg

常用注解

@Data 注解

放置在类上,为该类的所有属性添加get、set方法,为该类添加equals、canEquals、hashCode、toString方法。示例源码如下:

1
2
3
4
5
6
7
8

@Data
class Pc1 implements Serializable{
private static final long serialVersionUID = 1;
private int id;
private double prcie;
private String pcname;
}

编译后class文件如下,可以看到提供了上述提到的所有方法,并且源码依然很简洁

figure 2.jpg

@Getter、@Setter 注解

放置在类或属性上,为该类下全部属性或特定属性添加Get/Set方法。示例源码如下:

1
2
3
4
5
6
7
8
class Pc2 implements Serializable{
private static final long serialVersionUID = 1;
private int id;
@Setter
private double prcie;
@Getter
private String pcname;
}

编译后class文件如下,可以看到字段上添加了相应的方法

figure 3.jpg

@Accessors 注解

该注解会对通过Lombok生成的get、set方法进行调整,故使用该注解的同时,需要保证已经添加了 @Data 或 @Getter/@Setter注解。具体地:

  • 设置@Accessors注解的chain属性为true时,则属性的setter方法返回this而不是void
  • 设置@Accessors注解的fluent属性为true时,则setter、getter的方法名称不会以set、get作为前缀,而是直接使用字段名作为方法名。同时,当fluent属性为true时,chain属性默认为true,故setter方法也会返回this,而不是void
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
import lombok.*;
import lombok.experimental.Accessors;

public class AccessorsTest {

public static void main(String[] args) {
Cat cat = new Cat();
cat.setAge(18)
.setName("Tom");
String catName = cat.getName();
Integer catAge = cat.getAge();
System.out.println("cat name: " + catName);
System.out.println("cat age: " + catAge);
System.out.println("cat: " + cat.toString());

System.out.println("-------------------------------------------- ");

Pig pig = new Pig();
pig.name("Bob")
.age(24);
String pigName = pig.name();
Integer pigAge = pig.age();
System.out.println("pig name: " + pigName);
System.out.println("pig age: " + pigAge);
System.out.println("Pig: " + pig.toString());
}
}

@Data
// chain 为true 时,则setter方法返回this而不是void
@Accessors(chain = true)
class Cat {
private String name;

private Integer age;
}

@Getter
@Setter
@ToString
// fluent 为true 时,则setter、getter的方法名称不会以set、get作为前缀,而是直接使用字段名作为方法名
// 同时,当 fluent 为true 时,chain属性默认为true,故setter方法会返回this,而不是void
@Accessors(fluent = true)
class Pig {
private String name;

private Integer age;
}

figure 4.jpg

@EqualsAndHashCode 注解

为该类添加equals、canEqual、hashCode方法。示例源码如下:

1
2
3
4
5
6
7
@EqualsAndHashCode
class Pc5 implements Serializable {
private static final long serialVersionUID = 1;
private int id;
private double prcie;
private String pcname;
}

编译后class文件如下,该类添加了equals、canEqual、hashCode方法

figure 5.jpg

@ToString 注解

放置在类上,为该类添加toString方法。示例源码如下

1
2
3
4
5
6
@ToString
class Pc6 {
private int id;
private double price;
private String pcname;
}

编译后class文件如下,该类添加了toString方法

figure 6.jpg

callSuper 属性

对于子类而言,如果我们期望让子类的equals、hashCode、toString方法包含父类的属性,需要将子类上的@EqualsAndHashCode、@ToString注解的callSuper属性设置为True

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
import lombok.*;

public class CallSuperTest {
public static void main(String[] args) {
BigDog bigDog1 = new BigDog(1);
bigDog1.setName("Bob");
bigDog1.setType("big");

BigDog bigDog2 = new BigDog(1);
bigDog2.setName("Tony");
bigDog2.setType("big");

System.out.println("<Big Dog>: equals: " + bigDog1.equals(bigDog2));
System.out.println("<Big Dog>: hashcode 1: " + bigDog1.hashCode());
System.out.println("<Big Dog>: hashcode 2: " + bigDog2.hashCode());
System.out.println("<Big Dog>: toString 1: " + bigDog1.toString());
System.out.println("<Big Dog>: toString 2: " + bigDog2.toString());

SmallDog smallDog1 = new SmallDog(1);
smallDog1.setName("Bob");
smallDog1.setType("small");

SmallDog smallDog2 = new SmallDog(1);
smallDog2.setName("tony");
smallDog2.setType("small");

System.out.println("--------------------------------------------");
System.out.println("<Small Dog>: equals: " + smallDog1.equals(smallDog2));
System.out.println("<Small Dog>: hashcode 1: " + smallDog1.hashCode());
System.out.println("<Small Dog>: hashcode 2: " + smallDog2.hashCode());
System.out.println("<Small Dog>: toString 1: " + smallDog1.toString());
System.out.println("<Small Dog>: toString 2: " + smallDog2.toString());

}
}

@AllArgsConstructor
@NoArgsConstructor
@Data
class Dog {
private String name;

private String type;
}

@AllArgsConstructor
@EqualsAndHashCode
@ToString
class BigDog extends Dog {
private Integer id;
}

@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
class SmallDog extends Dog {
private Integer id;
}

效果如下所示

figure 7.jpg

@AllArgsConstructor、@NoArgsConstructor 注解

@AllArgsConstructor注解为该类添加一个全参构造器。需要注意的是:由于此时已经有构造器了,Java不再提供无参构造器。示例源码如下

1
2
3
4
5
6
7
@AllArgsConstructor
class Pc3 implements Serializable{
private static final long serialVersionUID = 1;
private int id;
private double prcie;
private String pcname;
}

编译后class文件如下,该类添加了一个全参构造器

figure 8.jpg

@NoArgsConstructor注解为该类添加一个无参构造器。示例源码如下:

1
2
3
4
5
6
7
@NoArgsConstructor
class Pc4 implements Serializable {
private static final long serialVersionUID = 1;
private int id;
private double prcie;
private String pcname;
}

编译后class文件如下,该类添加了一个无参构造器

figure 9.jpg

@Builder、@SuperBuilder 注解

@Builder为类提供了基于建造者模式的创建对象的方法

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
import lombok.*;

public class BuilderTest {
public static void main(String[] args) {
Animal animal = Animal.builder()
.id(996)
.type("dog")
.remark("this is a dog")
.build();
System.out.println("animal: " + animal);

Animal animal3 = new Animal();
animal3.setId(3);
System.out.println("animal3: " + animal3);

Animal animal2 = Animal.builder()
.id(2)
.build();
System.out.println("animal2: " + animal2);
}
}

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
class Animal {
private Integer id;

private String type = "CAT";

// 使用build方式创建对象时,如果未指定值,则使用该默认值
@Builder.Default
private String remark = "test";
}

效果如下所示。特别地,type属性虽然设置了默认值,但通过build方式创建对象时,如果没有设置该值,则其不会使用默认值。故可在有默认值的属性上添加 @Builder.Default 来解决该问题

figure 10.jpg

但对于子类而言,添加@Builder注解后,如果通过build方式创建子类对象时,无法设置父类属性值,只能设置子类自己的属性。故对于该问题,Lombok自1.18.2版本开始引入了@SuperBuilder注解。只需在父类、子类同时使用该注解即可

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
import lombok.*;
import lombok.experimental.SuperBuilder;

public class SuperBuildTest {
public static void main(String[] args) {
People people = People.builder()
.firstName("Bob")
.lastName("Wang")
.build();
System.out.println("people: " + people);

Man man = Man.builder()
.firstName("Tony")
.lastName("Li")
.age(18)
.weight(59.9)
.sex("man")
.build();
System.out.println("man: " + man);

Man man2 = Man.builder()
.firstName("Tom")
.age(18)
.build();
System.out.println("man2 :" + man2);

}
}

@ToString
@SuperBuilder
class People {
private String firstName;

private String lastName;
}

@ToString(callSuper = true)
@SuperBuilder
class Man extends People {
private Integer age;

private Double weight;

@Builder.Default
private String sex = "男人";
}

效果如下所示

figure 11.jpg

@NonNull 注解

放置在属性上,将对该属性进行非空检查,如果为空(null),将会抛出NullPointerException;同时为该类添加一个由所有@NonNull属性组成的有参构造器。需要注意的是:由于此时已经有构造器了,Java不再提供无参构造器。示例代码如下:

1
2
3
4
5
6
7
@Data
class Pc7 {
@NonNull
private Integer id;
private double price;
private String pcname;
}

编译后class文件如下,该类中对id属性的操作均进行了非空检查,添加了一个所有@NonNull属性组成(id属性)的有参构造器

figure 12.jpg

@RequiredArgsConstructor 注解

通常我们在实现Spring依赖注入,常常是在需要注入的属性添加@Autowired注解实现(如下所示)。但如果一个类下有很多属性需要注入时,@Autowired就要写一堆了。使用@RequiredArgsConstructor注解可以简化依赖注入

1
2
3
4
5
6
7
8
9
...
@Controller
public class Controller1{
@Autowired
private Service1 service1;
@Autowired
private Service2 service2;
...
}

这里可以使用这里@RequiredArgsConstructor注解来简化依赖注入操作:首先需要将该注解放在类上,然后在 需要注入的属性前添加final(用法1) 或 在需要注入的属性上添加注解@NonNull(用法2)。用法1 和 用法2 虽然效果一样,但用法1更常用

1
2
3
4
5
6
7
8
9
10
11
...
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Controller
public class Controller1{
// 用法 1
private final Service1 service1;
// 用法 2
@NonNull
private Service2 service2;
...
}

@Slf4j 注解

为该类添加一个属性名为log的SLF4J日志对象

示例源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class User {
private int id;
private String name;
private int age;

public int getAge() {
log.error("SLF4J getAge: " + age );
log.debug("SLF4J getAge: " + age );
return age;
}
...
}

编译后的class文件如下,该类添加了一个属性名为log的SLF4J日志对象

figure 13.jpg

@SneakyThrows注解

@SneakyThrows注解的作用是其会自动捕获方法中的受检异常,再将其转换为非受检异常并重新对外抛出。从而避免我们显式进行处理(方法签名声明异常抛出 或 捕获处理异常)

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
import lombok.SneakyThrows;
import java.io.IOException;

public class ExTest {
public static void main(String[] args) {
for (int i=1; i<4; i++) {
System.out.println("i --->>> "+i);
try{
int res = foo(i);
System.out.println("[Foo] --->>> " + res);
}catch (Exception e) {
e.printStackTrace();
}
}
}

@SneakyThrows
private static int foo(int a) {
if( a==1 ) {
throw new IOException("IO Error");
} else if( a==2 ) {
throw new ClassNotFoundException("System Error");
}

return a * 100;
}
}

结果如下所示,符合预期

figure 13.5.jpg

Note

  • 使用 @Accessors(fluent = true) 注解时,setter/getter的方法名称不会以set、get作为前缀,而是直接使用字段名作为方法名。可是如果某序列化工具是以set、get作为方法名称的前缀来查找相应字段的setter/getter的方法进行序列化、反序列化的话。即会出现序列化、反序列化失败的问题。典型地,存在此问题的序列化工具有:Fastjson

  • 对于 首字母小写、第二个字母大写的字段名,Lombok生成的setter/getter方法与Spring规范不兼容

例如对于下述类中的yMax属性而言,我们使用IDEA自动生成setter/getter方法,可以看到,其方法名中的y依然是小写

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BorderMsg {
private Integer yMax;

/* 使用IDEA自动生成的get、set方法 */
public Integer getyMax() {
return yMax;
}

public void setyMax(Integer yMax) {
this.yMax = yMax;
}
/*******************************/
}

而当我们使用Lombok生成setter/getter方法时,却发现其方法名中的Y却是大写

figure 14.jpg

对于Spring而言,针对首字母小写、第二个字母大写的字段名,其setter/getter方法名规范使用的是前者。即IDEA自动生成的字段名首字母依然保持小写的方法名,而非Lombok生成的字段名首字母为大写的方法名。故我们在使用Spring中相关工具类时(典型地:RestTemplate类),对于相关字段需通过IDEA来生成setter/getter方法,否则会导致序列化、反序列化失败

请我喝杯咖啡捏~
  • 本文作者: Aaron Zhu
  • 本文链接: https://xyzghio.xyz/Lombok/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处!

欢迎关注我的微信公众号:青灯抽丝