使用Lambda与Supplier/Consumer时遭遇java.lang.ClassCastException问题
解决Java 8 Supplier/Consumer建造者模式的属性null与类型转换异常问题
嗨,我来帮你梳理下这个问题的根源和解决办法~
问题根源分析
你遇到的两个问题其实是关联的:
- build()后属性为null:大概率是你的子类建造者只是把属性存在了自己的成员变量里,根本没把值传递给
Supplier创建的目标实例。比如你在setDomain()里给builder自己的domain字段赋值,但build()的时候只是调用supplier.get()新建了一个对象,完全没用到这个字段,自然是null。 - 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
相关产品推荐
相关产品推荐

