如何避免Lombok @Data、@Builder类含final字段的空指针异常
异常根因定位
你遇到的UnsupportedOperationException和业务转换逻辑里的空值判断没有关系,核心原因是Lombok的注解生成逻辑和类内带初始值的final字段的规则不兼容:
@Data注解会为所有非final字段生成公开setter方法,但final字段本身语义不允许修改,Lombok自然不会为其生成对应setter@Builder注解扫描类字段时,会把所有private修饰的字段(包括已直接写死初始值的final字段)纳入Builder参数构建链,但这个final字段既没有setter、也没有走构造方法的赋值流程,不管是Lombok自身的字段初始化逻辑,还是后续需要通过反射遍历类字段的组件(比如序列化工具、Bean映射组件),在尝试给这个字段赋值时,就会抛出No accessor to set property的异常。
你当前写的converter方法通过Builder构建对象的逻辑是没问题的,对象本身能成功创建,异常一般出现在对象创建完成后的反射操作环节(比如接口返回序列化、后续的Bean拷贝等流程)。
可落地的解决方法
- 方法1:给带初始值的final字段明确标注Builder默认值
在final字段上增加@Builder.Default注解,告知Lombok这个字段的初始值是默认值,生成Builder逻辑时要保留这个初始值,不要把它强制纳入需要从外部传入的构造参数列表。修改后的类代码如下:
这种方式能从字节码生成层面修正Lombok的逻辑偏差,从根源上避免反射组件找不到字段访问器的问题。@Data @Builder public class ClassA { private String id; @Builder.Default private final String name = "Name A"; } - 方法2:将Builder的生成逻辑绑定到自定义构造器上
如果你不希望Builder逻辑把带固定初始值的final字段纳入方法链,可以手写只包含可变字段的构造器,把@Builder注解放在这个构造器上,让Lombok只把构造器参数作为Builder的入参。示例代码:
这种写法下生成的Builder只会暴露@Data public class ClassA { private String id; private final String name = "Name A"; @Builder public ClassA(String id) { this.id = id; } }id的赋值方法,完全不会触碰name字段的赋值逻辑,自然不会触发后续的反射访问异常。 - 方法3:显式禁止final字段生成setter
给final字段增加@Setter(AccessLevel.NONE)注解,告知Lombok不要为这个字段生成任何setter方法,从语义上匹配final字段不可修改的特性,避免反射组件因为找不到匹配的setter抛异常。注意这个方法需要和方法1配合使用,否则还是会存在Builder扫描字段的逻辑冲突。
避坑提醒
不要为了消除异常,随意把final修饰去掉破坏字段不可变的语义,也不要手动编写空setter方法尝试给final字段赋值——这类写法不仅过不了编译检查,还会彻底破坏类的设计初衷。
内容的提问来源于stack exchange,提问作者jimmy
相关产品推荐
相关产品推荐

