Java Builder模式:可变对象引用引发已构建对象状态变更的合理性探讨
Builder模式中传递可变对象的行为是否可接受?
这种行为绝对不可接受——它直接破坏了不可变对象的核心设计原则:对象一旦创建,其状态就应该保持稳定,不能被外部任意修改。像示例里那样,外部修改传入Builder的原集合后,已构建完成的Foo实例内部状态被悄悄改变,很容易引发难以排查的bug,尤其是在多线程环境下,这种不可预测的状态变更会导致数据一致性问题。
问题根源
示例代码里的Builder直接持有外部传入的ArrayList引用,build()时只是把这个引用传给Foo,并没有做隔离。外部对原集合的修改自然会同步到Foo内部的集合上。
修复方案
有两种可靠的方式解决这个问题:
深拷贝可变对象
在Builder的setHobbies方法或者build方法中,对传入的可变集合做深拷贝,让Foo内部持有独立的副本,切断和外部集合的引用关联。示例代码:public final class Foo { private final String name; private final List<String> hobbies; private Foo(String name, List<String> hobbies) { this.name = name; this.hobbies = hobbies; } public static class Builder { private String name; private List<String> hobbies; public Builder setName(String name) { this.name = name; return this; } public Builder setHobbies(List<String> hobbies) { // 拷贝传入的集合,避免外部修改影响内部 this.hobbies = new ArrayList<>(hobbies); return this; } public Foo build() { return new Foo(name, hobbies); } } }提供逐个添加的方法
不让外部直接传入整个可变集合,而是给Builder提供addHobby这类方法,由Builder内部自己维护集合实例,完全控制集合的修改权限。同时在build时再做一次拷贝,确保Foo内部的集合不可被修改:public final class Foo { private final String name; private final List<String> hobbies; private Foo(String name, List<String> hobbies) { this.name = name; // 包装为不可修改列表,彻底禁止内部修改 this.hobbies = Collections.unmodifiableList(hobbies); } public List<String> getHobbies() { return hobbies; } public static class Builder { private String name; private List<String> hobbies = new ArrayList<>(); public Builder setName(String name) { this.name = name; return this; } public Builder addHobby(String hobby) { hobbies.add(hobby); return this; } public Foo build() { return new Foo(name, new ArrayList<>(hobbies)); } } }
最佳实践
强烈建议保持所有构建出的对象完全不可变:
- 把
Foo的成员变量设为final,确保初始化后不能被重新赋值; - 对于引用类型的成员,要么使用不可变类型(比如
ImmutableList),要么在构造时做深拷贝并包装为不可修改的实例; - 只提供读取状态的getter方法,不提供任何修改内部状态的接口。
只有这样,才能保证对象的状态稳定,避免外部代码的意外干扰,同时提升多线程环境下的安全性。
内容的提问来源于stack exchange,提问作者xyzcoder
相关产品推荐
相关产品推荐

