【转】Lombok Pojo默认初始值问题
Lombok以注解形式来简化java代码,提高开发效率。比如我们常用的@Builder
、@Data
、@AllArgsConstructor
、@NoArgsConstructor
、@ToString
等。
然最近在迭代中发现Lombok(version:1.16.20
或者低于这个版本)的builder模式与new实例化或者反射机制下实例化对象默认值不兼容。这里的默认值不是基本数据类型
Lombok是通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。可以通过反编译查看生成的字节码。例子:
1
2
3
4
5
6
7
8
9
10
|
@Builder @Data @ToString @AllArgsConstructor @NoArgsConstructor public class A { int num; Integer count; Integer noticedCount = 0 ; } |
使用方式如下
1
2
3
4
5
|
public class Test { public static void main(String[] args) { A a = A.builder().count( 1 ).noticedCount( 2 ).build(); } } |
这样写看着比以前的new A(),再set值方便多了,当然也可以在构造函数中直接传入需要的值。但是如果类的属性多了,就会发现Lombok使用以及开发效率上要高很多。
然而最近,在项目中使用的时候发现一个bug问题,项目中使用的Lombok的版本号1.16.20。如上面的例子,通过A.builder().build()
实例化后,发现a中的noticedCount
的默认值为null。究其原因,查看生成的class文件,有个A$Builder.class,使用javap -c A.class查看字节码或者直接将这个class文件拖拽到idea中,查看生成的代码,以下是在idea中展示class的代码
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
|
package com.test; public class A$ABuilder { private int num; private Integer count; private Integer noticedCount; A$ABuilder() { } public A$ABuilder num( int num) { this .num = num; return this ; } public A$ABuilder count(Integer count) { this .count = count; return this ; } public A$ABuilder noticedCount(Integer noticedCount) { this .noticedCount = noticedCount; return this ; } public A build() { return new A( this .num, this .count, this .noticedCount); } public String toString() { return "A.ABuilder(num=" + this .num + ", count=" + this .count + ", noticedCount=" + this .noticedCount + ")" ; } } |
从中看到noticedCount
默认值没有。看出A.builder().build()
中的build()方法构造A对象的时候是使用内部类的属性值,所以这个初始化的实例我们的noticedCount
值为空。
经过查看Lombok下的代码发现有个@Builder.Default
根据注释,这个是能解决初始化默认值的。代码如下
1
2
3
4
5
6
7
8
9
10
11
|
@Builder @Data @ToString @AllArgsConstructor @NoArgsConstructor public class A { int num; Integer count; @Builder .Default Integer noticedCount = 0 ; } |
再看看生成的A$Builder.class文件的内容如下
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
|
package com.test; public class A$ABuilder { private int num; private Integer count; private boolean noticedCount$set; private Integer noticedCount; A$ABuilder() { } public A$ABuilder num( int num) { this .num = num; return this ; } public A$ABuilder count(Integer count) { this .count = count; return this ; } public A$ABuilder noticedCount(Integer noticedCount) { this .noticedCount = noticedCount; this .noticedCount$set = true ; return this ; } public A build() { Integer noticedCount = this .noticedCount; if (! this .noticedCount$set) { noticedCount = A.access$ 000 (); } return new A( this .num, this .count, noticedCount); } public String toString() { return "A.ABuilder(num=" + this .num + ", count=" + this .count + ", noticedCount=" + this .noticedCount + ")" ; } } |
可以看到代码中多了private boolean noticedCount$set
;这个就是确认是否需要设置默认值。
到这一步你以为就完美了吗??NO.
假如我们在Test方法中增加一行代码,如下,自己可以试试运行的结果看看输出的a与a1的结果
1
2
3
4
5
6
7
8
|
public class Test { public static void main(String[] args) { A a = A.builder().count( 1 ).noticedCount( 2 ).build(); System.out.println(a); A a1 = new A(); System.out.println(a1); } } |
什么还需要new?有些场景中,比如其他第三方库使用这个类的时候,就不是通过builder模式来实例化对象,第三方库一般都是通过反射机制来实例化,然Lombok给我编译出来的class字节码已经不再是原有的。所以就出现问题了。
至于Lombok是如何实现的。可以研究下HandleBuilder
.里面有具体逻辑