当前位置: 首页 > news >正文

《Effective Java》第1条:用静态工厂方法代替构造器

如果提到如何创建一个类的对象,你的第一反应肯定是:new一个对象!也就是用构造器的方式。

但是在 Effective Java 这本书中,作者提出了另一个创建对象的思路,那就是静态工厂。

举个栗子:

第一种:正常情况:创建一个 “男人” 的对象

// Person类
public class Person {String sex;Person(String sex) {this.sex = sex;}Person() {this.sex = "男";}
}// Main类
public class Main {public static void main(String[] args) {Person man = new Person();Person woman = new Person("女");}
}

第二种:使用静态工厂:创建一个 “男人” 的对象

// Person类
public class Person {private static Person MAN = new Person("男");private static Person WOMAN = new Person("女");String sex;private Person(String sex) {this.sex = sex;}public static Person getMan() {return MAN;}public static Person getWoman() {return WOMAN;}
}// Main类
public class Main {public static void main(String[] args) {Person man = Person.getMan();Person woman = Person.getWoman();}
}

那么,使用静态工厂类的优缺点有什么呢?

优点1、原文P4:静态工厂方法与构造器不同的第一大优势在于,他们有名称。

从例子里也可以看到,如果用第一种方式创建的对象,我们都不能判断我们创建的是一个男人还是女人,或者通过参数的方式。但是第二种方式,我们通过方法名就可以判断是男人还是女人了。

优点2、原文P5:静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新的对象

类似于单例模式,如例子所示的第一种方式,每次创建对象使用new的方式,就会在堆内存中开辟一块空间用于存储对象,会导致在堆内存中创建多个一样的Person对象。

但是如果使用第二种静态工厂的方式,每次拿到的Person对象,其实都是一个,在内存中也只有这一个Person对象,这样就避免了占用过多的内存。Boolean.valueOf(boolean)就是使用了这种方式(如下),可以看出 TRUE 和 FALSE 都是事先定义好的对象,return 出去的对象也只有这两个其中的一个。

// jdk源码 Boolean 类
...
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
...
public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);
}

优点3、原文P5:静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象

我们把上面的例子里的Person类修改一下,改为以下:

// Person类
public abstract class Person {private static Person MAN = new Man();private static Person WOMAN = new Woman();String sex;private Person(String sex) {this.sex = sex;}public static Person getAnyone(double flag) {if (flag < 0.5) {return MAN;} else{return WOMAN;}}private static class Man extends Person {Man() {super("男");}}private static class Woman extends Person {Woman() {super("女");}}
}
// Main类
public class Main {public static void main(String[] args) {Person anyone = Person.getAnyone(0.8);}
}

该例子中,Man类 和 Woman类 都变成了Person 的子类,getAnyone方法可以返回任意的一个子类,但是Main方法中,没有做任何的修改,如果此时又有一个Badman类实现了Person,那么只需要在Person中加入一个静态成员变量和get方法即可。

书中所写到的,Collections类中,emptyList、singletonList,就是用了这种方式实现的。他们都实现了List接口,使用了静态方法返回不同的实现。

优点4、原文P6:静态工厂的第四大优势在于,所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。

翻译一下就是,每次调用所传递的参数不同,静态工厂可以根据参数的不同返回不同的类,如上例中的 getAnyone 方法。如果有一天,我们把 0.8,对于用户来说根本没有感知。

书中写到的 EnumSet 类,是个抽象类,有构造器,但是构造器是给子类实现来用的,比如静态工厂方法 noneOf,就用了该方式

// jdk源码 EnumSet 类
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {Enum<?>[] universe = getUniverse(elementType);if (universe == null)throw new ClassCastException(elementType + " not an enum");if (universe.length <= 64)return new RegularEnumSet<>(elementType, universe);elsereturn new JumboEnumSet<>(elementType, universe);
}

从源码可以看出,当 universe.length 64 的时候,返回的是 JumboEnumSet 子类。

优点5、原文P6:静态工厂的第五大优势在于,方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

字都认识,连接来就不知道啥意思了吧。

还是拿上个例子来说吧,getAnyone方法可以返回任意的一个子类,目前只有两个子类,分别是 男人类 和 女人类,假如未来又出来一个新的 外星人类,那么这个外星人类只需要实现Person类,就可以完美适配上面的代码。但现在 这个外星人类是不存在的。

书中的例子是JDBC的API,当开发人员编写JDBC的时候,可能某个数据库还不存在,假如现在我开发了一个“小猪数据库”,我只需要实现JDBC的API,就可以让Java连上我的数据库了

即:JDBC 中的 DriverManager.getConnection() 方法:

getConnection() 的返回类型是 Connection 接口(由 Java 标准库定义);

当 Java 团队编写 DriverManager 类时,Connection 的具体实现类(如 MySQLConnection、XiaoZhuConnection)根本不存在 —— 这些实现类是由数据库厂商(如 MySQL、小猪数据库)后来编写的;

但 DriverManager 依然可以通过静态工厂方法返回这些 “未来才会存在” 的实现类对象,只要它们实现了 Connection 接口。

源码(仅展示关键代码):

// 通过 info(即在application.yml中的配置项,来生成一个Connection的子类)
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {......for(DriverInfo aDriver : registeredDrivers) {if(isDriverAllowed(aDriver.driver, callerCL)) {try {println("    trying " + aDriver.driver.getClass().getName());// 从这里将配置文件中配置的信息,转换为 Connection,这样任意一个数据库都可以连接了Connection con = aDriver.driver.connect(url, info);if (con != null) {println("getConnection returning " + aDriver.driver.getClass().getName());return (con);}} catch (SQLException ex) {if (reason == null) {reason = ex;}}} else {println("    skipping: " + aDriver.getClass().getName());}}......
}

现在优点都讲完了,接下来看看缺点

缺点1、原文P7:类如果不含公有的或者受保护的构造器,就不能被子类化,但是内部类除外。

像是上面的例子,实现Person的两个类都是内部类,所以Person的构造器可以是private的,如果其中有一个类不是内部类,那么Person的构造器就不能是Persion的,否则就不能被子类继承。

缺点2、原文P7:程序员很难发现静态工厂方法

因为我们已经习惯了去new一个对象,如果一个对象的构造器不是private的,那么我们习惯性的就去new了。

所以书中给的解决方案是:通过注释,写明这个是静态工厂;或者遵守标准的命名习惯(具体看书P7)

http://www.lryc.cn/news/621551.html

相关文章:

  • 【R语言】R 语言中 gsub 与正则表达式详解(含 POSIX 与 Perl 风格实例)
  • 【R语言】更换电脑后,如何在新设备上快速下载原来设备的 R 包?
  • 智能体开发实战:用Deepseek做一个生成思维导图的智能体
  • 2025高防IP vs 普通IP:本质差异与选型指南
  • 移动板房的网络化建设
  • StarRocks集群部署
  • 39 C++ STL模板库8-容器1-array
  • 常见IP模块的仲裁策略和实现
  • YOLO11分割模型使用rknn2量化部署
  • 网络安全蓝队常用工具全景与实战指南
  • 【DDIA】第二部分:分布式数据
  • 从零到一:发布你的第一个 npm 开源库(2025 终极指南)
  • Elasticsearch赋能规章制度智能检索:从海量文档到秒级响应
  • app-5 控制卡升级
  • 【CV 目标检测】②R-CNN模型
  • 「iOS」————UITableView性能优化
  • GCC深度剖析:从编译原理到嵌入式底层实战
  • 阿里云出里两款新的云服务器
  • 基于单片机的超市储物柜设计
  • 打破传统局限,人工智能+虚拟仿真赋能日化品设计实验教学
  • 异步并发×编译性能:Dart爬虫的实战突围
  • 笔试——Day39
  • Python洛谷做题39:P5729 【深基5.例7】工艺品制作
  • 【题解|两种做法】[ZJOI2008] 洛谷 P2600 瞭望塔[半平面交]
  • 第十章 项目进度管理-10.3 规划进度管理
  • Mini MAX AI应用矩阵测评报告——基于旗下多款产品的综合体验与行业价值分析
  • 【大模型微调系列-02】 深度学习与大模型初识
  • 《WINDOWS 环境下32位汇编语言程序设计》第1章 背景知识
  • uniapp纯前端绘制商品分享图
  • MySQL 主键详解:作用与使用方法