为什么 Lombok 的 setter 不能链式调用?—— @Accessors(chain = true) 解惑

一、案例引入

在日常开发中,我们经常使用链式调用(Method Chaining)来提升代码的可读性和简洁性。例如,在构建一个业务对象并设置多个属性时:

relations.add(new ResourceRelation()
    .setResourceId(resourceId)
    .setBizType("course")
    .setBizId(courseId));

然而,在实际编码过程中,部分开发者(我的同事就)遇到了如下编译错误或 IDEA 提示:

No candidates found for method call
或
Cannot resolve method 'setBizType' in 'void'

这表示编译器认为 setResourceId() 方法返回的是 void,无法继续调用后续方法,导致链式调用失败。

二、问题现象(看看相关实体类)

  1. 实体类定义(使用 Lombok)
import lombok.Data;

@Data
public class ResourceRelation {
    private Long resourceId;
    private String bizType;
    private Long bizId;
}
  1. IDEA报错代码示例
// 报错:链式调用失败
relations.add(new ResourceRelation()
    .setResourceId(resourceId)
    .setBizType("course")  // IDEA 报错:Cannot resolve method
    .setBizId(courseId));

尽管使用了 @Data 注解,IDE 却提示无法链式调用 setter 方法。

三、原因分析

Lombok 的 @Data 或 @Setter 注解生成的 setter 方法默认返回 void,而非 this。
@Data 是一个组合注解,它内部包含了 @Getter、@Setter、@ToString 等。其中,@Setter 负责生成 setter 方法。
注意返回类型是 void,而非 this!
生成的等效 Java 代码如下:

public class ResourceRelation {
    private Long resourceId;
    private String bizType;
    private Long bizId;

    public void setResourceId(Long resourceId) {
        this.resourceId = resourceId;
    }

    public void setBizType(String bizType) {
        this.bizType = bizType;
    }

    public void setBizId(Long bizId) {
        this.bizId = bizId;
    }
}

由于 setResourceId() 返回 void,后续调用 .setBizType() 相当于在 void 上调用方法,语法错误,编译不通过。

四、解决方案

方案一:使用 @Accessors(chain = true)(推荐)

在实体类上添加 Lombok 的 @Accessors(chain = true) 注解,使生成的 setter 方法返回 this。

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)  // 关键:开启链式调用
public class ResourceRelation {
    private Long resourceId;
    private String bizType;
    private Long bizId;
}

补充说明:@Accessors 的其他能力

  • chain = true:返回 this,实现 setXxx().setYyy() 风格。
  • fluent = true:去除 set 前缀,实现 xxx().yyy() 风格(如 .name(“Tom”).age(25))。
  • 注意:chain 和 fluent 不能同时使用。

方案二:手动编写返回 this 的 setter

如果不使用 Lombok,或需精细控制,可手动实现链式 setter:

public class ResourceRelation {
    private Long resourceId;
    private String bizType;
    private Long bizId;

    public ResourceRelation setResourceId(Long resourceId) {
        this.resourceId = resourceId;
        return this;
    }

    public ResourceRelation setBizType(String bizType) {
        this.bizType = bizType;
        return this;
    }

    public ResourceRelation setBizId(Long bizId) {
        this.bizId = bizId;
        return this;
    }
}

方案三:使用 @Builder 构建器(适合复杂对象)

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ResourceRelation {
    private Long resourceId;
    private String bizType;
    private Long bizId;
}

使用方式:

ResourceRelation relation = ResourceRelation.builder()
    .resourceId(resourceId)
    .bizType("course")
    .bizId(courseId)
    .build();

优点:线程安全、不可变对象支持;缺点:不适用于需要频繁修改的实例。

方案四:放弃链式,使用传统 setter

如果无法修改实体类,或项目规范禁止链式调用,可采用传统方式:

ResourceRelation relation = new ResourceRelation();
relation.setResourceId(resourceId);
relation.setBizType("course");
relation.setBizId(courseId);
relations.add(relation);

注意事项与潜在风险

使用 @Accessors(chain = true) 很方便,但它也有一些问题

违背 JavaBean 规范:

JavaBean 规范要求 setter 方法返回 void。

继承场景下链式可能中断

若父类未启用 chain = true,子类调用父类 setter 后返回父类类型,链式调用会中断

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐