You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Lambda与Supplier/Consumer时遭遇java.lang.ClassCastException问题

解决Java 8 Supplier/Consumer建造者模式的属性null与类型转换异常问题

嗨,我来帮你梳理下这个问题的根源和解决办法~

问题根源分析

你遇到的两个问题其实是关联的:

  1. build()后属性为null:大概率是你的子类建造者只是把属性存在了自己的成员变量里,根本没把值传递给Supplier创建的目标实例。比如你在setDomain()里给builder自己的domain字段赋值,但build()的时候只是调用supplier.get()新建了一个对象,完全没用到这个字段,自然是null。
  2. ClassCastException:要么是你在强制转换时忽略了泛型的类型安全(比如用了原始类型的StanzaDataBuilder),要么是你的建造者继承逻辑里泛型绑定出了问题,导致类型擦除后转换失败。

解决方案

我们需要调整抽象建造者的设计,让它能收集对目标实例的修改操作,在build()时统一应用到Supplier创建的对象上,同时保证泛型类型安全。

1. 重构抽象建造类StanzaDataBuilder

修改抽象类,引入Consumer列表来收集所有对目标实例的修改逻辑:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

public abstract class StanzaDataBuilder<T> {
    private final Supplier<T> instanceSupplier;
    private final List<Consumer<T>> instanceModifiers = new ArrayList<>();

    protected StanzaDataBuilder(Supplier<T> supplier) {
        this.instanceSupplier = supplier;
    }

    // 子类调用这个方法添加属性修改逻辑
    protected void addModifier(Consumer<T> modifier) {
        instanceModifiers.add(modifier);
    }

    public T build() {
        // 先创建实例
        T targetInstance = instanceSupplier.get();
        // 执行所有修改操作
        instanceModifiers.forEach(modifier -> modifier.accept(targetInstance));
        return targetInstance;
    }
}

2. 实现子类RetailProfileDataBuilder

子类不再自己存储属性,而是把每个setXxx()方法的逻辑包装成Consumer传递给父类:

public class RetailProfileDataBuilder extends StanzaDataBuilder<RetailProfile> {

    private RetailProfileDataBuilder(Supplier<RetailProfile> supplier) {
        super(supplier);
    }

    public static RetailProfileDataBuilder of() {
        // 传入目标类的构造方法引用作为Supplier
        return new RetailProfileDataBuilder(RetailProfile::new);
    }

    public RetailProfileDataBuilder setDomain(String domain) {
        // 把设置domain的逻辑添加到修改器列表
        addModifier(profile -> profile.setDomain(domain));
        return this; // 链式调用
    }

    // 其他属性设置方法同理
    public RetailProfileDataBuilder setUserId(String userId) {
        addModifier(profile -> profile.setUserId(userId));
        return this;
    }
}

3. 正确使用建造者,避免类型转换异常

使用时直接用子类类型或者带泛型的父类类型,完全不需要强制转换:

// 方式1:直接用子类类型(推荐,最清晰)
RetailProfile profile = RetailProfileDataBuilder.of()
        .setDomain("retail")
        .setUserId("user_001")
        .build();

// 方式2:用带泛型的父类类型
StanzaDataBuilder<RetailProfile> builder = RetailProfileDataBuilder.of()
        .setDomain("retail");
RetailProfile profile = builder.build();

这样修改后:

  • build()返回的实例会正确带上所有设置的属性,不会再是null
  • 全程不需要强制转换,自然也就避免了ClassCastException

额外说明

如果你的RetailProfile是不可变类(没有setter方法),可以调整Supplier的逻辑,比如用带参数的构造方法,或者在Supplier里直接创建带初始属性的实例,再结合Consumer(如果是用建造者模式创建不可变类的话)。但上面的方案对可变类来说是最直接的解决办法。

内容的提问来源于stack exchange,提问作者Abhishek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 03:28:30