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

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));
            }
        }
    }
    

最佳实践

强烈建议保持所有构建出的对象完全不可变:

  1. 把Foo的成员变量设为final,确保初始化后不能被重新赋值;
  2. 对于引用类型的成员,要么使用不可变类型(比如ImmutableList),要么在构造时做深拷贝并包装为不可修改的实例;
  3. 只提供读取状态的getter方法,不提供任何修改内部状态的接口。

只有这样,才能保证对象的状态稳定,避免外部代码的意外干扰,同时提升多线程环境下的安全性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 08:04:45