为什么阿里开发手册推荐用静态工厂方法代替构造器?

🍅 作者简介:哪吒,CSDN2021博客之星亚军🏆、新星计划导师✌、博客专家💪
🍅 哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师
🍅 技术交流:定期更新Java硬核干货,不定期送书活动
🍅 关注公众号【哪吒编程】,回复 1024 ,获取《10万字208道Java经典面试题总结(附答案)》2024修订版pdf,背题更方便,一文在手,面试我有
目录
- 一、构造函数的问题
- 1、方法名都是一个,容易混淆
- 2、扩展性问题
- 3、构造函数每次被调用都要创建一个新对象
- 二、静态工厂方法如何解决构造器的问题呢?
- 问题1:方法名都是一个,容易混淆
- 问题2:扩展性问题
- 问题3:构造函数每次被调用都要创建一个新对象
- 三、实例受控
- 四、静态工厂方法可以返回任何子类型的对象
- 五、Java 8中允许接口包含静态方法了,和静态工厂方法有关系吗?
- 1、接口与静态方法
- 2、与默认方法结合使用
- 3、支持函数式接口
- 六、Java9中支持私有的静态方法,但静态字段必须是公有的
- 1、为什么Java9中支持私有的静态方法
- 2、为什么静态字段必须是公有的?
- 七、总结
在设计类时,我们经常为其提供公有的构造器,通过构造器来实例化类。
但,在我学习设计模式时,有一点经常被提及,用静态工厂方法代替构造器。
今天,就来分析一下其中的利与弊。
首先,要先弄懂,构造函数的问题是什么?
一、构造函数的问题
当类有多个构造器时,静态工厂方法可以通过有意义的名称来区分不同的创建方式,使代码更易读。
有的杠精可能要说话了:通过改变参数列表中参数类型的顺序或个数,我就可以提供多个不同的构造器,有什么问题吗?
我想说的是,你这样并不符合编码规范,有如下几个问题:
1、方法名都是一个,容易混淆
编码规约规定,方法名称见名知意,你TMD都是一个名,搞笑呢?
如果某人看到new Person(“John”, 25)或new Person(25, “John”),他可能会混淆它们的作用,尤其是当参数数量和类型相同而顺序不同的时候。
public class Person {private String name;private int age;private boolean isEmployed;public Person(String name, int age) {this.name = name;this.age = age;this.isEmployed = false; // 默认值}public Person(String name, boolean isEmployed) {this.name = name;this.age = 0; // 默认值this.isEmployed = isEmployed;}public Person(int age, String name) {this.name = name;this.age = age;this.isEmployed = false; // 默认值}
}
参数相同,顺序不同,谁是谁,你记得住吗?
参数顺序不同的构造器可能导致调用时不小心传入了错误顺序的参数,结果得到一个非预期的对象。
如果顺序被搞错,编译器不会报错,但会导致运行时的逻辑错误。
Person person = new Person("John", 25); // 想调用第一个构造器
Person person2 = new Person(25, "John"); // 想调用第三个构造器,结果参数位置错误,产生错误的数据
2、扩展性问题
当你需要扩展类并添加更多构造器时,如果通过改变参数顺序和数量来实现重载,可能会迅速导致构造器的组合方式爆炸,难以维护,估计连你自己都看不明白了。
这谁写的代码?拉出去砍了。
现在裁员盛行,你要小心了。
3、构造函数每次被调用都要创建一个新对象
这能有什么问题?这不都是Java中默认的嘛,每当你使用 new 关键字调用一个类的构造函数时,都会创建一个对象实例。
创建新对象涉及分配内存和初始化对象,这在性能上有一定开销。如果在短时间内需要频繁创建和销毁大量对象,这种开销可能会显著影响系统性能。
构造函数的每次调用都创建新对象,也会使对象的缓存和复用变得困难。
在数据库连接池中,我们希望复用已经存在的连接,而不是每次都创建一个新连接,这时构造函数的行为就显得不合适。
二、静态工厂方法如何解决构造器的问题呢?
问题1:方法名都是一个,容易混淆
静态工厂方法可以通过有意义的命名来避免这种混淆,见名知意。
这是静态工厂方法的一个重要优势。不同于构造函数必须与类名相同,静态工厂方法可以根据其创建对象的逻辑和用途使用不同的名称,从而提高代码的可读性和明确性。
通过静态工厂方法,如createChild、createEmployedAdult和createUnemployedAdult,你可以清晰地表达每个方法的用途,避免了通过构造器参数类型来区分的混淆问题。每个方法的名字直接说明了创建对象的用途,提升了代码的可读性。
改进后的代码:
public class Person {private String name;private int age;private boolean isEmployed;// 私有构造器,防止直接实例化private Person(String name, int age, boolean isEmployed) {this.name = name;this.age = age;this.isEmployed = isEmployed;}// 静态工厂方法,解决方法名混淆问题public static Person createChild(String name) {return new Person(name, 0, false); // 默认年龄为0,未就业}public static Person createEmployedAdult(String name, int age) {return new Person(name, age, true); // 成人,已就业}public static Person createUnemployedAdult(String name, int age) {return new Person(name, age, false); // 成人,未就业}
}
问题2:扩展性问题
静态工厂方法可以通过添加新的方法来扩展对象创建的方式,从而提高代码的扩展性。
假如需要增加新的构造方式(如裁员人员),你可以轻松地通过添加createRetiredPerson静态工厂方法来实现,而无需更改现有的构造函数或担心重载导致的代码复杂化。静态工厂方法的扩展性使得代码更容易维护和增强。
// 新增静态工厂方法,支持扩展需求
public static Person createLayoffPerson(String name) {return new Person(name, 35, true); // 默认35岁,在职
}
问题3:构造函数每次被调用都要创建一个新对象
静态工厂方法可以通过缓存对象、实现单例模式或其他优化策略,避免每次都创建新对象,从而提升性能和资源利用效率。
public class Person {private String name;private int age;private boolean isEmployed;private static final Person DEFAULT_CHILD_INSTANCE = new Person("Default Child", 0, false);// 私有构造器private Person(String name, int age, boolean isEmployed) {this.name = name;this.age = age;this.isEmployed = isEmployed;}// 使用缓存的实例,避免每次都创建新对象public static Person getDefaultChildInstance() {return DEFAULT_CHILD_INSTANCE;}// 其他静态工厂方法...
}
对于重复多次的调用,静态工厂方法可以返回同一个对象,这就可以控制存在哪些实例,被称为实例受控。
三、实例受控
遇到生僻词汇,哪吒的第一反应就是问问ChatGPT。

import java.util.HashMap;
import java.util.Map;public class Person {private String name;private int age;private boolean isEmployed;// 用于存储创建的实例private static final Map<String, Person> instances = new HashMap<>();// 私有构造器,防止直接实例化private Person(String name, int age, boolean isEmployed) {this.name = name;this.age = age;this.isEmployed = isEmployed;}// 静态工厂方法,解决方法名混淆问题,并实现实例受控public static Person createChild(String name) {String key = name + ":child";if (!instances.containsKey(key)) {instances.put(key, new Person(name, 0, false));}return instances.get(key);}public static Person createEmployedAdult(String name, int age) {String key = name + ":employed:" + age;if (!instances.containsKey(key)) {instances.put(key, new Person(name, age, true));}return instances.get(key);}public static Person createUnemployedAdult(String name, int age) {String key = name + ":unemployed:" + age;if (!instances.containsKey(key)) {instances.put(key, new Person(name, age, false));}return instances.get(key);}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + ", isEmployed=" + isEmployed + "}";}
}


通过静态工厂方法和实例缓存机制,你可以在重复调用时返回同一个对象,从而实现实例受控。这种设计可以精确控制一个类的实例数量,避免内存浪费,提高性能,同时提供了更好的代码管理和扩展性。
四、静态工厂方法可以返回任何子类型的对象
静态工厂方法可以返回Person类的任意子类型对象,而不仅限于返回Person本身。这种设计使得代码更具扩展性和灵活性。
通过静态工厂方法,子类的具体实现对外部是透明的。外部调用者不需要知道这些子类的存在或如何实现,只需通过工厂方法获取所需的对象。这种设计增强了封装性,方便后续扩展。
这种灵活性使得静态工厂方法比构造器更有优势,特别是在实现设计模式(如工厂模式、单例模式、享元模式)时。
我们创建几个Person类的子类,Child、EmployedAdult、UnemployedAdult。
每个子类代表不同类型的Person,具有特定的行为和属性。
例如,Child类的age默认设为0,isEmployed设为false,表示未就业;EmployedAdult和UnemployedAdult分别表示不同就业状态的成年人。
通过静态工厂方法分别创建并返回不同子类的实例,它们隐藏了子类的具体实现,调用者只需要通过这些方法创建对象,不需要知道具体的子类结构。
五、Java 8中允许接口包含静态方法了,和静态工厂方法有关系吗?
1、接口与静态方法
我觉得这一变革多少和静态工厂方法的使用有一定的关系,它提供了接口设计更大的灵活性和功能性。
在java 8之前,实用工具方法通常定义在单独的工具类中(例如Collections或Arrays),而不是直接在接口中。这导致了一些不便,比如工具类不能直接访问接口的内部细节。
允许接口包含静态方法,使接口本身能够提供多样化的对象创建逻辑,这种灵活性是传统构造器所无法提供的。
例如,你可以在接口中定义一个静态工厂方法来返回该接口的某个实现类的实例,这样做简化了客户端代码的使用,也隐藏了实现的细节。
public interface Person {String getName();int getAge();static Person create(String name, int age) {return new PersonImpl(name, age); // 返回接口的实现类实例}
}// 私有实现类
class PersonImpl implements Person {private final String name;private final int age;private PersonImpl(String name, int age) {this.name = name;this.age = age;}@Overridepublic String getName() {return name;}@Overridepublic int getAge() {return age;}
}// 使用示例
public class Main {public static void main(String[] args) {Person person = Person.create("哪吒", 30);System.out.println(person.getName() + " is " + person.getAge() + " years old.");}
}
2、与默认方法结合使用
Java 8还引入了默认方法,允许接口在提供方法签名的同时也提供默认实现。默认方法与静态方法结合,使得接口不仅可以提供默认行为,还可以提供创建实例的方法和相关工具。
这使得接口可以拥有类似于抽象类的功能,而无需引入多继承的复杂性。
3、支持函数式接口
Java 8引入了函数式接口的概念,特别是在Lambda表达式和方法引用中被广泛使用。静态方法可以用来提供工厂方法或其他辅助方法,支持和增强函数式接口的使用。
例如,在Comparator接口中,静态方法comparing是一个静态工厂方法,用于创建比较器,这在函数式编程中非常有用。
六、Java9中支持私有的静态方法,但静态字段必须是公有的
1、为什么Java9中支持私有的静态方法
(1)实现代码重用和封装
Java 9中支持私有的静态方法是为了增强代码的封装性和重用性,特别是在使用静态工厂方法代替构造器的场景中,私有静态方法可以帮助我们更好地组织和管理对象创建的复杂逻辑。
在这个例子中,validateName和validateAge是私有的静态方法,它们封装了对象创建时的验证逻辑。这些方法只在接口内部使用,并不会暴露给接口的使用者。这种设计提高了代码的复用性和封装性,同时使得静态工厂方法的实现更加简洁。
public interface Person {String getName();int getAge();static Person createChild(String name) {validateName(name);return new Child(name);}static Person createEmployedAdult(String name, int age) {validateName(name);validateAge(age);return new EmployedAdult(name, age);}// 私有静态方法,供内部逻辑使用private static void validateName(String name) {if (name == null || name.isEmpty()) {throw new IllegalArgumentException("Name cannot be null or empty");}}private static void validateAge(int age) {if (age < 18) {throw new IllegalArgumentException("Age must be at least 18");}}
}
(2)支持更复杂的对象创建逻辑
通过在接口中定义私有静态方法,可以在静态工厂方法中分解复杂的对象创建逻辑。这使得我们可以用静态工厂方法来代替构造器,同时保持代码的整洁和易于维护。
例如,一个静态工厂方法可能涉及多个步骤来创建对象,通过私有静态方法可以将这些步骤独立出来,使代码更易于理解和测试。
2、为什么静态字段必须是公有的?
如果静态字段是私有的,那么这些字段只能在接口内部使用,无法被接口的实现类或外部使用者访问,这将限制接口的功能性和应用场景。
静态字段必须是公共的,这是基于接口的契约性质和设计原则,确保接口的实现类和使用者可以一致地访问这些字段,从而保持接口的简洁性和实用性。
七、总结
使用静态工厂方法代替构造器是一种常见的设计模式,提供了比传统构造器更多的灵活性。
静态工厂方法允许命名,以提高代码的可读性和表达力,同时可以返回不同子类型的实例,而不仅限于返回类本身的实例。这种方法还支持缓存和对象复用,避免每次都创建新对象,从而提高性能。
随着Java 8和Java 9对接口功能的扩展,静态工厂方法还可以被集成到接口中,通过静态方法直接提供对象创建逻辑,结合私有静态方法,进一步优化代码的封装性和维护性。
总体而言,静态工厂方法在提升代码灵活性、可维护性和性能方面具有显著优势。
👉 GPT功能:
- GPT-4o知识问答:支持1000+token上下文记忆功能
- 最强代码大模型Code Copilot:代码自动补全、代码优化建议、代码重构等
- DALL-E AI绘画:AI绘画 + 剪辑 = 自媒体新时代
- 私信哪吒,直接使用GPT-4o

🏆文章收录于:100天精通Java从入门到就业
哪吒数年工作总结之结晶。
🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师。
华为OD机试 2023B卷题库疯狂收录中,刷题点这里
刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天CSDN在线答疑。

相关文章:
为什么阿里开发手册推荐用静态工厂方法代替构造器?
🍅 作者简介:哪吒,CSDN2021博客之星亚军🏆、新星计划导师✌、博客专家💪 🍅 哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师 🍅 技术交流:定期更新…...
前端写法建议【让项目更加易于维护】
背景 标题前提条件: 没有字典接口、或其他原因,需要前端手动维护的情况 示例环境:vue2,其他项目同理 示例 如果项目有某种类别,前端和后端约定好了,某些情况下,需要前端写死时。 比如有字段…...
EasyExcel 自定义转换器、自定义导出字典映射替换、满足条件内容增加样式,完整代码+详细注释说明
虽然最之前是在其他地方看到的,但最终因缘巧合下找到了原文,还是尊重一下原作者。 参考引用了这位佬的博客,确实方便使用。 https://blog.csdn.net/qq_45914616/article/details/137200688?spm1001.2014.3001.5502 这是一个基于Easyexcel通过…...
C语言学习笔记 Day10(指针--中)
Day10 内容梳理: 目录 Chapter 7 指针 7.4 指针 & 数组 (1)指针操作数组元素 (2)指针加减运算 1)加法 2)减法 (3)指针数组 7.5 多级指针 Chapter 7 指针 …...
网页显示打印 pdf
文件服务使用 minio,使用 nginx 反向代理。 将文件存放在 minio 上,如果是公开的文件,则统一放到一个桶,设置为公开只读。 如果是私有文件,则使用临时链接,给有权限的用户查看和打印。 要实现在 html 页…...
1948-2024.5金融许可信息明细数据
1948-2024.5金融许可信息明细数据 1、时间:1948-2024.5 2、指标:来源表、机构编码、机构名称、所属银行、机构类型、业务范围、机构住所、地理坐标、行政区划代码、所属区县、所属城市、所属省份、邮政编码、发证日期、批准日期、发证机关、流水号、是…...
【笔记】从零开始做一个精灵龙女-画贴图阶段(终)
这篇主要是细节,包括花纹和其它一些细化 皮肤 脖子 脖子一定要压暗,不然前后关系体现不出来 脸 1. 忘了有uv缝了,记得打开投影模式画 顺着头发轨迹长的方向画出发际线 背包手镯 1.先画出暗色花纹: 2.再加亮色,亮…...
从MySQL到Elasticsearch:创建酒店索引案例
在现代的数据管理中,Elasticsearch(简称ES)因其强大的搜索功能和灵活的索引结构而受到广泛欢迎。本篇博客将介绍如何根据MySQL数据库中的酒店表定义,创建一个相应的Elasticsearch索引。 MySQL与Elasticsearch的对比 在开始之前&…...
Webkit与Web Push API:提升用户体验的推送技术
Web Push API是一种允许网站向用户发送通知的Web技术,即使用户没有打开网站也能接收到信息。这项技术可以显著提升用户的参与度和满意度。Webkit,作为Safari和其他浏览器的内核,对Web Push API的支持情况如何?本文将深入探讨Web P…...
Java线程池的拒绝策略
在 Java 线程池中,常见的拒绝策略: AbortPolicy(中止策略) 特点:直接抛出 RejectedExecutionException 异常来拒绝新任务的提交。应用场景:适用于对系统的稳定性要求较高,不希望丢失任务&#…...
【C++进阶】继承
【C进阶】继承 🥕个人主页:开敲🍉 🔥所属专栏:C🥭 🌼文章目录🌼 1. 继承的概念及定义 1.1 继承的概念 1.2 继承定义 1.2.1 定义格式 1.2.2 继承父类成员访问方式的变化 1.3 继承类模…...
立体相机镜面重建(一)镜面标定
无论是单目、双目或者是多屏幕镜面重建,都需要事先对屏幕和相机的相对位置进行标定,求得相机到屏幕之间的相对变换关系。如果求得屏幕和相机之间的变换关系呢?接下来是标定流程。 (一)准备: 1)…...
【如何有效解决前端Vue中的常见难题】
🐟作者简介:一名大三在校生,喜欢编程🪴 🐙个人主页🥇:Aic山鱼🐠WeChat:z7010cyy 🦈系列专栏:🏞️ 前端-JS基础专栏✨前端-Vue框架专栏…...
CLAMP-1靶机渗透测试
一、靶机下载地址 https://www.vulnhub.com/entry/clamp-101,320/ 二、信息收集 1、主机发现 # 使用命令 nmap 192.168.145.0/24 -sn | grep -B 2 "00:0C:29:88:B4:BF" 2、端口扫描 # 使用命令 nmap 192.168.145.0/24 -p- -sV 3、指纹识别 # 使用命令 whatweb …...
JavaScript中的Truthy Falsy值以及等号判断
1.Falsy & Truthy Falsy的值false,0,-0, “”, null, undefined,NaNTruthy的值除了以上之外的其他值 2.等号判断 console.log(10 10); console.log(0 ); console.log(0 false); console.log( fa…...
uniapp——展开和收起
案例展示 代码 后台返回的数据格式如下: {1: "大富科技速度快放假手动阀",2: "第三方斯蒂芬斯蒂芬是的开发时间",4: "45345345",5: "电饭锅电饭锅地方" }<view class"tipTitle">温馨提示</view> &l…...
WebGL2学习(2): GLSL ES 3.0
更多精彩内容尽在 dt.sim3d.cn ,关注公众号【sky的数孪技术】,技术交流、源码下载请添加VX:digital_twin123 WebGL 2.0 给 GLSL 带来了重大变化。WebGL 1.0 中使用的 GLSL 版本是 GLSL ES 1.0。 WebGL 2.0 中仍然可用。但是,通过编…...
[大模型实战] DAMODEL云算力平台部署LLama3.1大语言模型
[大模型实战] DAMODEL云算力平台部署LLama3.1大语言模型 目录 一、LLama3.1二、DAMODEL云算力平台2.1 提供的服务2.1.1 AI训练2.1.2 AI推理2.1.3 高性能计算2.1.4 图像/视频渲染2.1.5 定制化部署 2.2 支持的GPU 三、在DAMODEL部署LLama3.13.1 在DAMODEL创建实例&…...
驱动开发系列09 - Linux设备模型之设备,驱动和总线
一:概述 Linux 设备模型(LDM)是 Linux 内核中引入的一个概念。用于管理内核对象(那些需要引用计数的对象、例如文件、设备、总线甚至驱动程序),以及描述它们之间的层次结构,以及这些内核对象之间绑定关系。Linux 设备模型引入了对象生命周期管理、引用计数、以及面向对象…...
HTML实现弹出层
leopard/ˈlepərd/ 豹子,豹纹 弹出层指的是鼠标悬停于某个元素之上时显示的一个界面组件。 关注和理解特性:z-index属性和动态生成HTML元素。 HTML5新增: figure:媒体内容(图像,音频,视频),用于包含一…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
