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

Java中Builder设计模式的必要性及与链式setter的对比

先直接说核心偏差

你想的那种new Person().setXxx()链式写法,本质是给可变对象做了个流畅接口,和经典Builder模式的适用场景差得很远——最核心的问题是,这种写法从语法层面就不可能构造出全final修饰的不可变对象,根本达不到博客里那段Builder代码要实现的效果。

两种写法的直观对比

先把你说的链式set实现贴出来,问题一眼就能看到:

public class Person {
    // 这里绝对不能加final,因为set方法要在对象new出来之后改属性
    private String name;
    private int age;
    private String idCard;
    private String address;

    // 必须对外暴露无参构造,不然没法new Person()起手
    public Person() {}

    // 每个set返回this实现链式调用
    public Person setName(String name) {
        this.name = name;
        return this;
    }
    public Person setAge(int age) {
        this.age = age;
        return this;
    }
    public Person setIdCard(String idCard) {
        this.idCard = idCard;
        return this;
    }
    public Person setAddress(String address) {
        this.address = address;
        return this;
    }
    // 剩下的getter省略
}

这种写法的使用方式确实和Builder长得像:

Person p = new Person()
        .setName("张三")
        .setAge(25)
        .setIdCard("110xxx")
        .setAddress("北京");

但你拿到的这个p,是个随时能被改的可变对象,任何地方拿到p的引用,都能再调用p.setAge(-10)给你改个非法值进去,多线程场景下根本没法安全使用。

再看博客里的Builder实现,核心是把“可变的构造过程”和“不可变的最终对象”拆成了两个类:

public class Person {
    // 所有属性全final,对象初始化完就再也改不了
    private final String name;
    private final int age;
    private final String idCard;
    private final String address;

    // 构造方法私有,外部根本没权限直接new Person
    private Person(PersonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.idCard = builder.idCard;
        this.address = builder.address;
    }

    // 只提供getter,没有任何setter,从根源堵死修改可能
    public String getName() {return name;}
    public int getAge() {return age;}
    public String getIdCard() {return idCard;}
    public String getAddress() {return address;}

    public static class PersonBuilder {
        // 只有构造阶段用的Builder是可变的,属性不需要加final
        private String name;
        private int age;
        private String idCard;
        private String address;

        public PersonBuilder setName(String name) {
            this.name = name;
            return this;
        }
        public PersonBuilder setAge(int age) {
            this.age = age;
            return this;
        }
        public PersonBuilder setIdCard(String idCard) {
            this.idCard = idCard;
            return this;
        }
        public PersonBuilder setAddress(String address) {
            this.address = address;
            return this;
        }

        public Person build() {
            // 统一做参数校验,不合法直接抛错,绝对不会生成残缺对象
            if (age < 0 || age > 150) throw new IllegalArgumentException("年龄非法");
            if (idCard == null || idCard.length() != 18) throw new IllegalArgumentException("身份证号非法");
            return new Person(this);
        }
    }
}

Builder模式比链式set强在哪

  • 第一优先级是支持不可变对象:这是你说的链式set方案永远做不到的。不可变对象没有线程安全问题,可以放心当缓存key、在多线程间传递、作为方法返回值,完全不用担心被下游代码篡改,这也是Java后端开发里最推荐的DTO、值对象的写法。
  • 构造阶段有统一的校验出口:所有参数校验逻辑都可以放在build()方法里,只要对象构造成功,就一定是合法的。链式set的写法没有这个统一节点,你完全可以写new Person().setName("张三")就把对象传出去用,漏设了age、idCard都没人拦着,线上跑起来才会抛空指针。
  • 语法层面强制必填参数:如果name、idCard是必填项,你可以直接给Builder加带参构造方法public PersonBuilder(String name, String idCard),用户new Builder的时候不传这两个参数编译就过不去,从根源上避免漏参数。链式set根本做不到这种强制约束。
  • 避免半构造对象外泄:Builder模式下,你在set属性的过程中持有的一直是Builder实例,不会对外暴露还没构造完成的Person对象,直到调用build()才会生成最终可用的实例。链式set的写法从new Person()开始拿到的就是Person实例,很可能中途被其他代码拿去调用,拿到的是个属性还没设全的残次品。

当然也不是说链式set完全没用:如果你就是要做一个可变的配置类、参数极少、没有不可变需求,那链式set写起来更简单,完全可以用。但只要涉及不可变值对象、参数多、有校验规则、多线程场景,Builder的优势是链式set替代不了的。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 21:42:16