news 2026/2/7 3:31:10

踩坑!Lombok @Builder和@NoArgsConstructor一起用,默认值居然失效了

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
踩坑!Lombok @Builder和@NoArgsConstructor一起用,默认值居然失效了

踩坑!Lombok @Builder和@NoArgsConstructor一起用,默认值居然失效了

前几天维护一个老接口,新服务对接的时候突然报空指针异常,排查了半天,最后发现居然是Lombok注解用错了导致的。

说真的,Lombok这东西平时用着是真方便,@Data、@Builder一套注解甩上去,getter、setter、构造器全不用自己写,省了好多代码。但也正因为太方便,有时候忽略了注解之间的搭配问题,很容易踩坑。

这次踩的坑,就是@Builder、@NoArgsConstructor、@AllArgsConstructor和@Data一起用,导致DTO里的默认值失效,进而引发了空指针。今天就把这个坑整理出来,看看有没有朋友和我一样,也栽过这个跟头。

先上我最开始写的DTO代码,看着其实没什么问题:

importlombok.AllArgsConstructor;importlombok.Builder;importlombok.Data;importlombok.NoArgsConstructor;@Data@Builder@NoArgsConstructor@AllArgsConstructorpublicclassReqDto{// 给field1设置了默认值trueprivateBooleanfield1=true;privateStringfield2;privateStringfield3;}

接口里的判断逻辑也很简单,就是用field1作为条件之一,判断field2和field3不为空:

publicvoiddoBusiness(ReqDtoreqDto){// 这里报了空指针,reqDto.getField1()居然是nullif(reqDto.getField1()&&reqDto.getField2()!=null&&reqDto.getField3()!=null){// 执行业务逻辑System.out.println("业务逻辑执行成功");}}

当时看到空指针报错,我第一反应是新服务对接时,没给field1传值。但找对接的同事确认后,他们说field1是可选参数,没传的时候就用默认值。

我就纳闷了,我明明给field1设置了默认值true,就算没传值,也应该是true才对,怎么会是null呢?

后来我把DTO的class文件反编译了一下,看完瞬间就明白了,问题出在Lombok的@Builder注解上。

先给大家看一下,上面那套注解,反编译后的DTO代码是什么样的(关键部分截取):

publicclassReqDto{privateBooleanfield1=true;privateStringfield2;privateStringfield3;// @NoArgsConstructor生成的无参构造器publicReqDto(){}// @AllArgsConstructor生成的全参构造器publicReqDto(Booleanfield1,Stringfield2,Stringfield3){this.field1=field1;this.field2=field2;this.field3=field3;}// @Builder生成的builder内部类和相关方法publicstaticReqDto.ReqDtoBuilderbuilder(){returnnewReqDto.ReqDtoBuilder();}publicstaticclassReqDtoBuilder{privateBooleanfield1;privateStringfield2;privateStringfield3;ReqDtoBuilder(){}publicReqDto.ReqDtoBuilderfield1(Booleanfield1){this.field1=field1;returnthis;}publicReqDto.ReqDtoBuilderfield2(Stringfield2){this.field2=field2;returnthis;}publicReqDto.ReqDtoBuilderfield3(Stringfield3){this.field3=field3;returnthis;}publicReqDtobuild(){returnnewReqDto(this.field1,this.field2,this.field3);}}// 下面是@Data生成的getter、setter方法,省略...}

重点看builder的build方法,它调用的是全参构造器new ReqDto(this.field1, this.field2, this.field3)。

而builder内部类里的field1,是没有设置默认值的,默认就是null。当我们用builder创建对象,又没有给field1赋值的时候,builder里的field1就是null,然后通过全参构造器传给DTO的field1,直接覆盖了我们在DTO里设置的默认值true。

这就是问题的根源!我们以为给field1设置了默认值就万事大吉,但@Builder注解生成的代码,直接把这个默认值给“覆盖”掉了。

举个例子,当我们用builder创建对象,只传field2和field3,不给field1传值:

ReqDtoreqDto=ReqDto.builder().field2("test2").field3("test3").build();

这时reqDto.getField1()的值,不是我们预期的true,而是null。接口里用这个null去做&&判断,自然就报空指针了。

找到问题之后,解决办法就很简单了,有两种常用的方式,根据自己的场景选就行。

第一种方式:给@Builder的field设置默认值(推荐)

不用改其他注解,直接在@Builder注解里给需要默认值的字段设置默认值,这样builder创建对象时,就算不赋值,也会用我们设置的默认值。

importlombok.AllArgsConstructor;importlombok.Builder;importlombok.Data;importlombok.NoArgsConstructor;@Data// 给field1设置默认值true,覆盖builder内部的null默认值@Builder(field1=true)@NoArgsConstructor@AllArgsConstructorpublicclassReqDto{privateBooleanfield1=true;privateStringfield2;privateStringfield3;}

这样修改后,再用builder创建对象,不给field1赋值,field1就会是true,和我们预期的一致。

第二种方式:不用@AllArgsConstructor,手动写全参构造器(灵活度高)

如果不想用@Builder的field默认值设置,也可以去掉@AllArgsConstructor注解,自己手动写全参构造器,在构造器里给field1设置默认值。

importlombok.Builder;importlombok.Data;importlombok.NoArgsConstructor;@Data@Builder@NoArgsConstructor// 去掉@AllArgsConstructor,手动写全参构造器publicclassReqDto{privateBooleanfield1=true;privateStringfield2;privateStringfield3;// 手动写全参构造器,给field1设置默认值publicReqDto(Booleanfield1,Stringfield2,Stringfield3){this.field1=field1==null?true:field1;this.field2=field2;this.field3=field3;}}

这种方式的好处是,就算field1传了null,也能通过构造器的判断,把它设为true,避免空指针。

这里还有个小细节,很多人可能会忽略:如果去掉@AllArgsConstructor,只保留@Builder和@NoArgsConstructor,Lombok会自动生成一个全参构造器吗?

答案是不会。@Builder注解本身不会生成全参构造器,它只是依赖全参构造器来创建对象。如果我们不手动写,也不用@AllArgsConstructor,编译的时候就会报错,提示找不到全参构造器。

再补充一个点,为什么我们给DTO的field1设置了默认值,还是会被覆盖?

因为Java的赋值顺序是:先执行字段的默认值赋值,再执行构造器里的赋值。我们用builder创建对象时,调用的是全参构造器,构造器里的this.field1 = field1(builder里的null),会覆盖掉字段本身的默认值true。

就像下面这段简单的代码,执行完之后,field1的值是null,而不是true:

publicclassTest{privateBooleanfield1=true;publicTest(Booleanfield1){this.field1=field1;}publicstaticvoidmain(String[]args){Testtest=newTest(null);System.out.println(test.field1);// 输出null}}

道理是一样的,Lombok生成的代码,本质上也是这样的逻辑,只是我们平时看不到而已。

最后再总结一下这个坑,用大白话讲,就是:

当@Builder和@NoArgsConstructor、@AllArgsConstructor一起使用时,@Builder生成的builder类,其内部字段默认是null,build方法会调用全参构造器,用builder里的null覆盖DTO字段的默认值,导致默认值失效。

解决办法就两种:要么用@Builder(field = 默认值)给字段设置默认值,要么手动写全参构造器,在构造器里处理默认值。

其实Lombok的坑还有不少,但大多都是因为对注解的底层实现不了解,盲目搭配使用导致的。平时用的时候,多留意一下注解之间的兼容性,必要的时候反编译看一下生成的代码,就能避免很多不必要的麻烦。

希望我这个踩坑经历,能帮大家避开这个Lombok的小陷阱,以后写代码少走点弯路~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/6 22:14:55

掌握AI写教材方法,低查重不是梦,轻松搞定教材编写难题!

传统教材编写困境与AI的解决方案 编写教材时离不开资料的支撑,但传统的资料整合方法已难以满足现代需求。在以前,从课程标准的文件、学术论文到教学案例,这些信息散落在知网、教研平台等多个渠道,想要挑选出有用的信息常常需要耗…

作者头像 李华
网站建设 2026/2/7 21:40:59

低查重AI教材生成攻略:从选题到完稿的一站式解决方案

教材编写的挑战与AI工具的解决方案 在教材编写的初始阶段,完成初稿的过程似乎只是个开始,接下来的修改和完善可谓是一次“大考验”。仔细阅读整篇文章,寻找逻辑问题和知识点错误,实在需要耗费不少时间。每当需要调整某一章的结构…

作者头像 李华
网站建设 2026/2/6 21:38:28

构建一个论文学习AI助手

说实话:我曾经害怕阅读研究论文。密集的数学、符号繁重的证明、假设你已经了解另外三篇论文才能理解这篇论文。作为ML工程师,我知道这些论文包含了可以在工作中应用的见解,但实际提取这些价值感觉就像一件苦差事。 我的学习风格与传统学术写…

作者头像 李华
网站建设 2026/2/7 20:25:38

MX播放器 2.7.2 |安卓解码最强的媒体播放器

MX Player被誉为移动平台最强大的本地视频播放器。它以其卓越的解码性能、友好的字幕支持以及兼容各种特效字幕和在线匹配字幕的功能而受到用户的广泛好评。无论是安卓手机还是电视端,MX Player都能提供出色的观看体验,包括小窗口播放功能,使…

作者头像 李华