【Effective Java 条目二】-- 当构造器参数较多时考虑使用生成器

参数较多时存在的问题

当一个类需要多个参数(尤其是大部分为可选参数)时,传统方案存在明显缺陷:

传统模式的缺点

  1. 重叠构造器(Telescoping Constructor) :为不同参数组合创建多个构造器,参数越多,构造器数量呈指数级增长,可读性极差,调用时容易混淆参数顺序(如new User(1, "张三", 25, null, "male", " Beijing"),难以快速对应参数含义)。

实例:

java 复制代码
public class User {
    // 必要参数
    private final long id;
    private final String name;
    // 可选参数
    private final int age;
    private final String email;
    private final String gender;
    private final String address;

    // 仅包含必要参数的构造器(基础版)
    public User(long id, String name) {
        this(id, name, 0); // 调用下一个构造器,默认age为0
    }

    // 增加1个可选参数(age)
    public User(long id, String name, int age) {
        this(id, name, age, null); // 默认email为null
    }

    // 再增加1个可选参数(email)
    public User(long id, String name, int age, String email) {
        this(id, name, age, email, "unknown"); // 默认gender为unknown
    }

    // 再增加1个可选参数(gender)
    public User(long id, String name, int age, String email, String gender) {
        this(id, name, age, email, gender, "unknown"); // 默认address为unknown
    }

    // 包含所有参数的构造器(完整版)
    public User(long id, String name, int age, String email, String gender, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
        this.gender = gender;
        this.address = address;
    }
}
java 复制代码
// 只想设置id、name、address,但必须传递age、email、gender的默认值
User user = new User(1001, "张三", 0, null, "unknown", "北京");
  • 参数越多,构造器数量爆炸(n个参数需要n个构造器);
  • 调用时难以区分参数含义(如0null到底对应哪个属性),容易因顺序错误导致 bug;
  • 可读性极差,维护成本高。
  1. JavaBean 模式 :通过无参构造器创建对象,再用setter方法设置属性。缺点是:

    • 对象创建过程中可能处于 "不一致状态"(部分属性未设置完成时被使用);
    • 无法保证对象不可变(setter破坏封装性,多线程环境下需额外处理同步)。

核心思路 :通过无参构造器创建对象,再用setter方法逐一设置属性。

java 复制代码
public class User {
    // 必要参数(非final,允许修改)
    private long id;
    private String name;
    // 可选参数
    private int age;
    private String email;
    private String gender;
    private String address;

    // 无参构造器(必须)
    public User() {}

    // 所有参数的setter方法
    public void setId(long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

//调用方式:
User user = new User();
user.setId(1001);
user.setName("张三");
user.setAddress("北京"); // 按需设置属性

问题

  • 对象创建过程中处于 "不一致状态"(例如仅调用setId后就被其他线程使用,导致属性不完整);
  • 无法保证对象不可变(setter方法允许随时修改属性),多线程环境下需额外处理同步;
  • 缺少参数校验的 "时机"(若在setter中校验,仍可能出现部分属性未校验的中间状态)

解决方案:Builder模式(生成器)

核心思想 :不直接通过构造器或setter创建对象,而是先通过一个 "生成器(Builder)" 对象设置所有参数,最后调用build()方法生成不可变的目标对象。

java 复制代码
public class User {
    // 必要参数
    private final long id;
    private final String name;
    // 可选参数
    private final int age;
    private final String email;
    private final String gender;
    private final String address;

    // 私有构造器,仅允许Builder调用
    private User(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
        this.gender = builder.gender;
        this.address = builder.address;
        // 参数验证(如id不可为负,name不可为空)
        if (id <= 0) throw new IllegalArgumentException("id must be positive");
        if (name == null || name.isEmpty()) throw new IllegalArgumentException("name is required");
    }

    // 静态内部类:Builder
    public static class Builder {
        // 必要参数(无默认值,必须设置)
        private final long id;
        private final String name;
        // 可选参数(有默认值)
        private int age = 0;
        private String email = null;
        private String gender = "unknown";
        private String address = "unknown";

        // Builder的构造器:仅初始化必要参数
        public Builder(long id, String name) {
            this.id = id;
            this.name = name;
        }

        // 链式设置方法(返回Builder自身)
        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

        // 其他setter...

        // 构建目标对象
        public User build() {
            return new User(this);
        }
    }
}

调用方式:

java 复制代码
User user = new User.Builder(1001, "张三")
    .age(25)
    .email("zhangsan@example.com")
    .address("北京")
    .build();

Builder 模式的优势

  1. 可读性强 :参数名与值一一对应(如.age(25)),无需记忆参数顺序;
  2. 保证对象不可变 :目标类所有属性为final,且无setter,创建后状态固定,线程安全;
  3. 参数验证集中 :在build()方法中统一验证,避免对象处于不一致状态;
  4. 支持可变参数集合 :可在Builder中灵活处理多个同类型参数(如addHobby(String hobby))。

适用场景与注意事项

  • 适用场景:类有多个参数(尤其是可选参数多),或未来可能扩展更多参数的场景(如 POJO、配置类)。

  • 注意事项

    • 增加了代码量(需定义Builder类),但对于参数多的类,可读性提升远大于代码量增加的成本;
    • Builder不适合参数极少的类(如仅 2-3 个必要参数,直接用构造器更简洁);
    • 若目标类需继承,Builder的实现会更复杂(需考虑子类Builder与父类的兼容性)。

生成器模式非常适合类层次结构

与此同时,在《Effective Java》条目 2 中,提到 "生成器模式非常适合类层次结构",核心是指:当存在父类与子类构成的继承关系(类层次结构)时,使用生成器(Builder)可以优雅地解决多参数构造场景下的继承兼容性问题,避免传统构造器或 JavaBean 模式在继承中出现的混乱

具体场景与问题

假设存在一个类层次结构:比如父类Shape(图形),子类Circle(圆形)、Rectangle(矩形)。每个类都有自己的必要参数和可选参数(如ShapecolorCircle额外有radiusRectangle额外有widthheight)。

若用传统构造器实现继承,会出现两个问题:

  1. 子类构造器必须传递父类的所有参数 ,导致参数列表冗长且混乱(如Circle的构造器可能需要(color, radius, ...)Rectangle则需要(color, width, height, ...));
  2. 无法灵活扩展:若父类新增参数,所有子类的构造器都需修改,维护成本极高。

生成器模式如何适配类层次结构?

核心方案是:让父类和子类各自定义对应的 Builder,并通过 "泛型 + 递归类型参数" 让子类 Builder 能返回自身类型,实现链式调用的连贯性。具体步骤如下:

1. 父类定义抽象 Builder(泛型约束)

父类的 Builder 为抽象类,通过泛型指定 "子类 Builder 的类型",并定义父类属性的设置方法(返回泛型类型,确保子类调用时仍能链式返回子类 Builder)。

示例(父类Shape):

java 复制代码
public abstract class Shape {
    private final String color; // 父类的必要参数

    // 父类的构造器,由子类Builder调用
    protected Shape(Builder<?> builder) {
        this.color = builder.color;
    }

    // 父类的抽象Builder,泛型T约束为子类Builder
    public abstract static class Builder<T extends Builder<T>> {
        protected String color; // 父类的参数

        // 父类参数的设置方法,返回T(子类Builder类型)
        public T color(String color) {
            this.color = color;
            return self(); // 关键:返回子类Builder实例
        }

        // 抽象方法:由子类实现,返回自身(确保链式调用类型正确)
        protected abstract T self();

        // 抽象build方法:由子类实现,返回具体子类实例
        public abstract Shape build();
    }
}

2. 子类定义具体 Builder(继承父类 Builder 并指定泛型)

子类的 Builder 继承父类 Builder,并将泛型参数指定为自身类型(如Circle.Builder extends Shape.Builder<Circle.Builder>),同时添加子类特有的参数设置方法,最终通过build()返回子类实例。

示例(子类Circle):

java 复制代码
public class Circle extends Shape {
    private final double radius; // 子类的必要参数

    // 子类的构造器,接收子类Builder
    private Circle(Builder builder) {
        super(builder); // 调用父类构造器,传递父类参数
        this.radius = builder.radius;
    }

    // 子类的Builder,泛型指定为自身(Circle.Builder)
    public static class Builder extends Shape.Builder<Builder> {
        private final double radius; // 子类的必要参数(必须在Builder构造器中初始化)

        // 子类Builder的构造器:初始化子类的必要参数
        public Builder(double radius) {
            this.radius = radius;
        }

        // 实现父类的self(),返回当前Builder实例(确保链式调用类型正确)
        @Override
        protected Builder self() {
            return this;
        }

        // 实现build(),返回Circle实例
        @Override
        public Circle build() {
            return new Circle(this);
        }
    }
}

3.调用方式

java 复制代码
// 创建Circle:先设置父类参数(color),再通过build()返回Circle
Circle circle = new Circle.Builder(5.0) // 子类必要参数radius
    .color("red") // 父类参数,返回Circle.Builder,支持链式调用
    .build();

核心优势

  1. 链式调用连贯性 :通过泛型T extends Builder<T>self()方法,子类 Builder 调用父类的设置方法后,仍能返回子类 Builder 类型 ,确保链式调用不中断(如circleBuilder.color().build()仍能正确返回Circle)。
  2. 参数隔离与扩展:父类参数由父类 Builder 管理,子类参数由子类 Builder 管理,新增父类参数时,仅需修改父类 Builder,子类无需变动;新增子类参数时,仅需扩展子类 Builder,不影响父类和其他子类。
  3. 类型安全 :编译期即可保证build()返回的是具体子类类型(如Circle.Builderbuild()返回Circle),无需强制类型转换。
相关推荐
野生技术架构师6 小时前
牛客网Java 高频面试题总结(2025最新版)
java·开发语言·面试
纪莫6 小时前
技术面:SpringBoot(springboot的类加载和传统的双亲委派有什么区别、如何按顺序实例化Bean)
java·spring·java面试⑧股
kyle~6 小时前
CPU调度---协程
java·linux·服务器·数据库·c++20
会飞的小蛮猪6 小时前
Skywalking运维之路(Skywalking服务搭建)
java·运维·监控
L.EscaRC7 小时前
Redisson在Spring Boot中的高并发应用解析
java·spring boot·后端
他们叫我技术总监7 小时前
从开发者视角深度评测:ModelEngine 与 AI 开发平台的技术博弈
java·人工智能·dubbo·智能体·modelengine
李辉20037 小时前
Python逻辑运算符
java·网络·python
摇滚侠7 小时前
Spring Boot3零基础教程,StreamAPI 介绍,笔记98
java·spring boot·笔记
扫地僧过江南7 小时前
Kanass零基础学习,如何进行任务管理
java·禅道·项目管理工具