Effective Java 第三版读后感第二条:多个构造器参数时考虑构建器(建造者设计模式)

Application-mobile:单代码源-Mobile app source code 移动应用 单代码源 立即下载

在上一篇中介绍了构造器创建对象和使用静态工厂创建对象的相关内容,如果你想了解更多可以访问这篇文章 :第一条 使用静态工厂方法代替构造器

重叠构造器的弊端

静态工厂和构造器它们都有个共同的局限性:多个构造器可选参数的问题,当一个类有很多个成员变量,有些参数是必须的,有些参数是可选的,这样的构造器方法将会有多个。通常这种情况下,程序员一般会考虑到重叠构造器(telescoping constructor)模式。这里以类ThreadPoolExecutor类构造器为例:如下是我们通常创建线程池的一种构造方法:

// 创建核心线程是100,最大线程数200,空闲时间为5000s,任务队列是大小的ArrayBlockingQueue队列
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 200, 5000L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));

我们点进去会发现,ThreadPoolExecutor一共提供了4个构造器方法如下:
在这里插入图片描述
这里为了方便就以方式1和方式4为例,这里方式1是我们刚刚上面创建ThreadPoolExecutor方式,这里我们使用参数列表最短的构造器,即使用了重叠构造器如下代码所示:

// 方式1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
                          // 这里调用了方式4的构造器方法
 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
     Executors.defaultThreadFactory(), defaultHandler);
 }

 // 方式4
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          hreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这里ThreadPoolExecutor类也就提供3个重叠构造器的方法。这里我们可以试想一下,假设一个类有很多属性,对应的重叠构造器方法也很多,对应多个重叠构造器的方法,容易传错或者把两个参数的顺序颠倒了,也许编译器并不会报错,但是运行一般就会有问题,所以重叠构造器适合类成员属性少的情况下使用。

JavaBean方式 弊端

当遇到多个构造器参数时,可以使用javaBean模式代替上面的重叠构造器的方式,在这种方式下:通常是调用无参构造器的方式,然后在调用属性的setter方法设置相应的属性值,例如简单对象Student:

@Setter
@Getter
@NoArgsConstructor
@ToString
@AllArgsConstructor
public class Student {

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 年级
     */
    private int schoolGrade;
    /**
     * 学校
     */
    private String schoolName;

    /**
     * 班级
     */
    private String schoolClass;

创建对象进行赋值我们只需要这样做:就像下面5个步骤对属性进行一个个调用setter方法进行赋值。
在这里插入图片描述
但是这种方式有个很严重的确点,因为在构造属性是进行多次set调用进行赋值,这儿就会造成线程安全问题,为什么这么说呢?在完成对象完全初始化前后,多线程情况下读取对象的属性值将会出现不一致的情况,如下面图所示:
在这里插入图片描述
图画的丑…但是正如这份丑图展示的那样:线程B和C前后获取的javaBean对象Student在构造过程中,两个线程获取的age属性不一致。Joshua Bloch大神也说了这种需要程序员额外付出努力来确保javaBean的线程安全。

Builder方式

使用建造者设计方式,可以保证重叠构造器的安全性(因为初始化对象的时候,构造器只会被执行一次),也可以像javaBean方式那样的可读性。这种模式通常并不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者是静态工厂方式)得到一个builder对象,然后客户端在builder对象调用类似setter方法那样,来设每个相关的可选参数。最好在调用无参build方法,生成客户端所需要的方式。下面看看Joshua Bloch的示例:

// Builder Pattern  (Page 13)
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values
        private int calories      = 0;
        private int fat           = 0;
        private int sodium        = 0;
        private int carbohydrate  = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }
		// 这里return,是为了每次设置值都返回一个Builder对象
        public Builder calories(int val)
        { calories = val;      return this; }
        public Builder fat(int val)
        { fat = val;           return this; }
        public Builder sodium(int val)
        { sodium = val;        return this; }
        public Builder carbohydrate(int val)
        { carbohydrate = val;  return this; }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

通过Builder的构造进行赋值返回的是Builder本身,这样就得到了一个流式的API,就像下面这样调用一样:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
    .calories(100).sodium(35).carbohydrate(27).build();

除此之外还有另一种方式类层次结构如下:

public abstract class Pizza {
    public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
    final Set<Topping> toppings;
    abstract static class Builder<T extends Builder<T>> {
        //   create a second set which is empty
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);

        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }
        abstract Pizza build();
        protected abstract T self();
    }

    Pizza(Builder<?> builder) {
        // 复制builder对象的值
        toppings = builder.toppings.clone();
    }
}

有2个具体的pizza对象如下:

public class NyPizza extends Pizza {
    public enum Size { SMALL, MEDIUM, LARGE }
    private final Size size;

    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;

        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }

        @Override public NyPizza build() {
            return new NyPizza(this);
        }

        @Override protected Builder self() { return this; }
    }

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

public class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder<Builder> {
        private boolean sauceInside = false; // Default

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        @Override public Calzone build() {
            return new Calzone(this);
        }

        @Override protected Builder self() { return this; }
    }

    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }
}

客户端这样调用:

  NyPizza pizza = new NyPizza.Builder(SMALL)
                .addTopping(SAUSAGE).addTopping(ONION).build();
        Calzone calzone = new Calzone.Builder()
                .addTopping(HAM).sauceInside().build();

总结

与构造器相比,Builder模式灵活,可以利用单个builder构建多个对象。这里的是线程安全的,且可读性也像javaBean那样setter方法那样。如果类的构造器或者静态工厂中具有多个参数,设计这种类就可以使用Builder模式。

【Proteus】NE555纯硬件实现LED呼吸灯效果,附源文件,效果展示 本设计通过NE555与三极管的巧妙配合,仅用基础元件实现了呼吸灯效果。核心在于利用RC积分电路将PWM信号转换为控制电压,并通过负反馈动态调节占空比。Proteus仿真验证了方案的可行性,读者可进一步优化参数或扩展功能,探索更多硬件设计可能性。 阅读详情

相关推荐

大模型抽取知识图谱三元组的可行性分析

通过结合传统信息抽取模型与大语言模型的混合架构,可高效完成从学术文献到知识图谱三元组的全流程构建。分阶段优化:传统模型负责粗粒度抽取,大模型负责细粒度修正。灵活提示设计:通过少样本学习引导大模型理解领域本体。严格质量控制:结合规则引擎和人工校验,确保三元组准确性。该方案可显著提升知识图谱构建效率,适用于学术研究、企业知识库等场景。

qq_20647053的博客 1066

Gathering Initial Troubleshooting Information for Analysis of ORA-4031 Errors on the Shared Pool

In this Document Purpose Troubleshooting Steps References APPLIES TO: Oracle Database - Enterprise Edition - Version 8.0.6.3 and later In...

weixin_30549175的博客 168

KANO分析模型

KANO分析模型的基本概念,目的意义,应用场景,实现方法和示例等

JasonH2021的博客 3066

向你推荐---AppFuse(E文章转载)

AppFuse: Start Your J2EE Web Appsby Matt Raible07/15/2004ContentsThe Birth of AppFuseWhat Is AppFuse?What Does the Future Hold?AppFuse LightHow Do I Start Using

oceayan的专栏 1322

告别点不亮!STM32F103驱动HT1621段码屏的常见坑点与调试心得

本文详细解析了STM32F103驱动HT1621段码屏的五大常见问题及解决方案,包括硬件连接、时序控制、RAM地址映射等关键难点。通过实测数据和代码示例,帮助开发者快速排查显示异常问题,提升驱动开发效率。特别针对HT1621与STM32F103的兼容性问题提供了专业调试建议。

weixin_30925435的博客 379

扬声器设计避坑指南:用Comsol分析反电动势对THD的影响(附案例文件)

本文深入探讨了扬声器设计中反电动势对总谐波失真的关键影响。通过COMSOL仿真,详细分析了BL值非线性、音圈电感变化等电磁-力-声耦合机制,并提供了从参数化建模到系统级优化的实战指南,帮助工程师精准量化并降低谐波失真,提升扬声器音质。

xx56789的博客 520

深度分析Spring中的构造器注入

1. 示例 先来看一个例子,看看什么是构造器注入。 这里我写了一个类,分别有两个构造器,一个是注入一个Bean的构造器,一个是注入两个Bean的构造器: public class ConstructorAutowiredTest { private User user; private Role role; public ConstructorAutowiredTest() { } public ConstructorAutowiredTest(User

z69183787的专栏 2029

[原创]Keil工程STM32F103C8T6无缝转换到STM32F103RCT6

有个项目,老版本是STM32F103C8T6,硬件资源不够了,新版本采用STM32F103RCT6。 无缝过渡,如下: 选定芯片 宏定义改为STM32F10X_HD,USE_STDPERIPH_DRIVER 删除原有的启动汇编文件,更换为高密度器件汇编文件 时钟系统紊乱? 那得看你程序怎么写: 先看F103时钟树 int main(void) { unsigned char stat...

江东风又起 2万+

Effective java 学习第二条-遇到多个构造器参数时要考虑使用构建器

本文探讨了处理大量可选参数的三种设计模式:重叠构造器、JavaBeans模式和建造者模式,并分析了各自的优缺点。重叠构造器虽然安全但会导致组合爆炸和可读性问题;JavaBeans模式提高了可读性但牺牲了不可变性和线程安全;而建造者模式完美结合了两者优势,通过链式调用确保对象创建的一致性和可读性,同时支持不可变对象和参数校验,是处理复杂可选参数的最佳实践。

m0_46557728的博客 1079

【大模型工程局】告别单打独斗:手把手带你构建多智能体系统(MetaGPT 启示录)

想象一下,不再是只有一个全知全能的 AI,而是有一群各有所长、能够独立思考和行动的“智能体”(Agent)。它们生活在一个共同的环境中,可以相互交流、协作完成共同目标,也可能为了有限资源而相互竞争。这就是多智能体系统(MAS)的核心图景。MAS 的关键特征:自治性 (Autonomy):每个智能体都能独立感知环境、做出决策并执行动作,无需外部直接控制。交互性 (Interaction):智能体之间可以通过某种通信机制(显式或隐式)交换信息、协调行为。

高校图书馆高级系统架构师/研究馆员,推动高校AI从概念演示到可评审、可交付、可运维:路线图与立项评审、PoC交付、评测回归与上线运维(Baseline/Eval/Ops)。 1184

【图论】系列(七)图论的中心性度量方法及重要性评价指标

图论中的中心性度量方法是用来衡量图中各个节点(或顶点)相对重要性的指标。

qq_30066733的博客 1077

解读 Java 经典巨著《Effective Java》90条编程法则,第2条:遇到多个构造器参数时要考虑使用构建器

《Effective Java》是由 Joshua Bloch 撰写的经典书籍,提供了 Java 编程中的最佳实践和建议。在书中的第2条建议“遇到多个构造器参数时要考虑使用构建器”,主要是为了处理构造器参数过多时的设计问题。这条建议的主要目的是简化构造器的使用,增加代码的可读性和维护性。

赵士杰——成功并非一蹴而就,而是在持之以恒的努力中逐渐实现。 1532

rosbag分析传感器数据的时间同步情况方法

ROS传感器时间同步分析流程:通过rosbaginfo初步检查bag文件信息,使用rqt_bag可视化时间线分布,采用rostopicecho或Python脚本提取各传感器时间戳并计算时间差统计值(均值/最大差/标准差),最后分析接收与采样时间延迟。量化评估同步精度,识别固定偏移或随机抖动问题,确保多传感器数据对齐,为后续融合处理提供可靠数据基础。

start_up_go的博客 201

《Effective Java》第2条:遇到多个构造器参数时要考虑使用构建器

在《Effective Java》一书中,第2条建议强调了在遇到多个构造器参数时,应考虑使用构建器(Builder)模式。这一建议旨在解决当类的构造器参数过多时,代码可读性和可维护性下降的问题。

探_无止境的博客 959

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Effective Java 2 遇到多个构造器参数时要考虑使用构建器

第2个经验法则:用遇到多个构造器参数时要考虑使用构建器(consider a builder when faced with many constructor parameters)上一条讨论了静态工厂相对于构造器来说有五大优势。但静态工厂和构造器有个共同的局限性:它 们都不能很好地扩展到大量的可选参数。对于需要多参数的类,应该用哪种构造器或者静态工厂来编写呢?接下来,我将通过Java代码示例来对比分析构造器模式、JavaBeans模式以及建造者模式在处理多参数情况下的应用。

cangshanjiang的博客 1117

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Effective Java 第二条:遇到多个构造器参数时要考虑用构建器

对于Java处理多参数时,先给大家上两个程序员平时最喜爱用的写法: 第一:重叠构造器 public class NutritionFacts { //重叠构造器 private final int servingSize; private final int servings; private final int calories; private fin

生活不只眼前的苟且,还有诗和远方的田野。 737

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Effective Java笔记:遇到多个构造器参数时要考虑使用构建器

是一个专门用于弥补多参数构造器弊端的设计模式。通过引入一个 “构建器类(Builder)”,可以灵活地构造复杂对象,同时具有更好的可读性和易用性。假设我们设计一个用来表示食品配置信息的类。在《Effective Java》的。来替代传统的构造器或静态工厂方法。

eclipseC的博客 524

Effective Java 第三版 中文翻译

effective-java-third-edition 介绍 Effective Java 第三版全文翻译,纯属个人业余翻译,不合理的地方,望指正,感激不尽! 目录 推荐序 前言 致谢 第一章 引言 第二章 创建和销毁对象   本章涉及创建和销毁对象,包括何时以及如何创建它们,何时以及如何避免创建它们,如何确保它们被及时销毁,以及如何管理在销毁之前必须进行的清理操作。 第1项:考虑静态工...

Coloured_Glaze的博客 3861

【Effective Java】多构造器参数使用构建器 (快速上手)

简洁教程 第二条:遇到多个构造器参数考虑使用构建器

麻辣香蝈蝈的博客 1257

effective java——遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器都有个共同的局限性,它们都不能很好的扩展到大量的可选参数。 解决方式1、重叠构造器模式虽然可行,但是当有许多参数的时候,客户端代码会变得很难写,因为你并不能准确的知道这个参数到底对应哪个实参,很有可能就会不小心颠倒顺序而发生错误,并且就算写对了,对于阅读者也是极不友好的,后期维护困难。 解决方式2、Javabeans模式,在这种模式下,先调用一个无参构造器来创建对象,再通过调用se...

monkey-jie的博客 298

Effective Java:遇到多个构造器参数时要考虑使用构建器

1 如何创建有大量可选参数的类实例? 1.1 重叠构造器(telescoping constructor)模式 public class Student { /*必填参数*/ private String name; private int age; /*可选参数*/ private String sex; private String grade;...

添仔哥哥 461

Effective Java (2) - 遇到多个构造器参数时要考虑用构建器

一、背景 对于有多个可选参数的类,我们一般通过什么办法传递参数呢?这里提供了三种办法: ①. 重叠构造器模式 ②. JavaBeans模式 ③. Builder构建器模式 下面我们来分析一下以上三种方法的优势及弊端。 二、重叠构造器模式 重叠构造器模式中第一个构造器中只有必要参数,第二个构造器有一个可选参数,第三个构造器中有两个可选参数,依次类

技术人生 1657

《Effective Java》第2条:遇到多个构造器参数时要考虑用构建器

原文地址: https://itweknow.cn/detail?id=47 , 欢迎大家访问。 我们构建一个对象的几种方式 构造器 静态工厂方法 构建器 在书中提到了当我们遇到了大量的可选参数时,获取对象实例的几种方式(重叠构造方法,无参构造器配合JavaBean的setter方法,构建器),书中也分别列出了使用这几种方式的优劣之处。 重叠构造器 我们这里也以一个实际的例子入手,我们当前...

playing_the_fool的博客 430

Effective Java 2.2——遇到多个构造器参数时要考虑用构建器

大家还记得第一条的静态工厂吗?静态工厂来代替构造器有4个优点:自定义方法名;避免创建过多的实例;隐藏子类的实现细节;在泛型类里面可以节约客户端代码。 这一篇博客我们就来了解一下构建器。什么是构建器,可能大家没听说过这个名字。。。

秋千不会坠的博客 406

Effective Java 创建和销毁对象 2.遇到多个构造器参数时要考虑用构建器

静态工厂跟构造器都有一个共同的局限性:不能很好的扩展到大量的参数。 例: package com.example.yancy.yancy; /**  * Created by yancy on 2017/8/24.  */ public class Person {     /**      * 姓名      */     private Stri

yancyang的博客 2万+
上一篇: Redis事务机制详解
下一篇: scala集合泛型数据类型转换成java集合遇到的问题
Galen-gao
博客等级 码龄9年 931粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值