news 2026/3/26 17:53:55

踩坑!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/3/14 20:53:51

互联网大厂Java求职者面试实录:涵盖Spring Boot、微服务与AI技术

互联网大厂Java求职者面试实录:涵盖Spring Boot、微服务与AI技术 在互联网大厂Java求职面试中,面试官严肃而专业,而求职者谢飞机则时而机灵,时而搞笑,本文通过模拟面试场景,展现三轮循序渐进的技术提问&am…

作者头像 李华
网站建设 2026/3/13 19:21:21

计算机毕设Java基于Java的图书借阅系统 基于Java技术的智能图书管理系统设计与实现 Java驱动的图书借阅与管理一体化平台开发

计算机毕设Java基于Java的图书借阅系统l161s9 (配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。随着信息技术的飞速发展,传统的图书借阅模式已经难以满足现代图书馆和读…

作者头像 李华
网站建设 2026/3/14 20:18:18

用AI生成网络小说脑洞:技术实操指南,从Prompt到落地全拆解

在网络小说创作中,脑洞是作品的核心竞争力,新颖、有逻辑、可延伸的脑洞,能让作品在海量内容中快速脱颖而出。随着AI大模型技术的普及,越来越多创作者开始用AI辅助生成小说脑洞,但多数人仅停留在浅层使用,难…

作者头像 李华
网站建设 2026/3/13 15:57:56

扶贫实习支教管理系统的设计与实现 开题报告

目录 扶贫实习支教管理系统的设计与实现开题报告介绍研究背景与意义系统目标与功能模块技术选型与实现路径创新点与预期成果研究计划与难点 项目技术支持可定制开发之功能亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作 扶贫实习支教管…

作者头像 李华