effective java 3th item1:考虑静态工厂方法代替构造器
传统的方式获取一个类的实例,是通过提供一个 public
构造器。这里有技巧,每一个程序员应该记住。一个类可以对外提供一个 public
的 静态工厂方法 ,该方法只是一个朴素的静态方法,不需要有太多复杂的逻辑,只需要返回该类的实例。
这里通过 Boolean
(是原始类型 boolean
的包装类)举一个简单的例子:
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
这个方法,将一个 boolean
原始类型的值转换为 Boolean
对象引用。
值得注意的是,本条目中说的一个 静态工厂方法 不同于 设计模式 的工厂模式[1],同样的,本条目中描述的静态工厂方法在设计模式找不到对应的模式。
一个类可以对外提供静态工厂方法,来取代 public
的构造器,或者与 public
构造器并存,对外提供两种方式获取实例。用静态工厂方法取代 public
构造器,既有优势也有缺点。
优势体现在下面几点:
静态工厂方法与构造器比起来,它可以随意命名,而非固定的与类的名字保持一致。
如果一个构造器的参数本身,不能对将要返回的对象具有准确的描述。此时使用一个具有准确描述名字的静态工厂方法是一个不错的选择。它可以通过名字对将要返回的对象,进行准确的描述。使得使用的人可以见名知意。
举个例子,
BigInteger(int, int, Random)
构造器,返回的值可能是素数,因此,这里其实可以有更好的表达,通过使用一个静态工厂方法BigInteger.probablePrime(int, int, Random)
该方法于1.4
被加入。一个类只能有一个指定方法签名[2]的构造器。通常我们都知道如何绕过这限制,通过交换参数列表的顺序,得到不同的方法签名。但是这是一个很糟糕的主意。这给使用
api
的开发人员造成负担,他们将很难记住哪一个方法签名对应哪一个对象的返回,最后往往都是错误的调用。阅读代码的人同样也蒙圈,如果没有相应的文档告诉他们,不同的方法签名对应的构造器返回的对象是什么。静态工厂方法不受上述限制,不需要去通过交换参数顺序来彼此区分,因为它们可以拥有自己的名字。因此,当一个类的多个构造器,方法签名差不多,仅仅参数顺序不一样的时候,考虑使用静态工厂方法,仔细的为静态工厂方法取名字,以区分它们之间的不同。
静态工厂方法与构造器比起来,不必每次调用都创建新的对象
这允许不可变的类使用预创建的实例[3],或者在创建实例的时候,将实例缓存起来[4],重复的使用该实例,避免创建不重要的重复对象。
Boolean.valueOf(boolean)
方法使用该技巧,它永远都不会创建对象,返回的都是预创建好的对象。这个技巧有点类似于设计模式中的享元模式[5] 。它能大幅度的提高性能,特别是在特定场景下:一些对象创建的时候,需要花费很大性能,并且这些对象经常被使用。该特性允许类在任何时候,对其产生多少实例具有精确的控制。用这种技巧的类,被称为实例受控的类。这里有几个使用实例受控类的理由。实例受控允许一个类保证它是一个单例或者不可实例化的类。同样的,实例受控,也可以保证不可变类不会存在两个相等的实例。
静态工厂方法与构造器比起来,可以返回该类的任意子类型的对象
具有足够的灵活性,在获取对象的时候。可以返回协变类型,在方法中使用该类的子类构造器创建对象,然后返回,同时对外不需要暴露这些子类对象,适合于面向接口编程。在
1.8
之前,接口中不能有静态方法,针对情况的惯例做法是,针对名为Type
类型的接口,它的静态方法被放在一个不可实例化的类Types
中[6],典型的例子是java.util.Collections
类,它通过静态工厂方法,可以构建返回各式各样的集合:同步集合、不可修改的集合等等,但是返回的时候都是返回接口类型,具体实现类型不对外公开。// Collections 中非公开类,同步map
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
// Collections 的静态工厂方法,返回接口接口map,但是内部是返回同步Map类型。
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
起到简化
api
的作用,这种简化不仅仅是api
体积上的减少,减少对外暴露的类的数量,也给程序员直观上的简洁,他们只需要记住接口类型即可,无需记住具体的实现类型。这样也督促程序员面向接口编程,这是一种好的习惯。1.8
以后,接口可以写静态方法。因此,不再需要按照以前的习惯,为接口,写一个对应的类,直接在接口中写静态工厂方法。但是关于返回的实现类型,依然应该继续隐藏,使用非公开类实现。静态工厂方法与构造器比起来,可以随着传入参数的不同,返回不同的对象。
可以返回任何子类型,和第三条一样,但是可以继续添加控制,根据传入参数的不同,返回不同的对象。
一个例子,
EnumSet
,是一个不可实例的类,只有一个静态工厂方法,没有构造器。但是在openJDK
的实现中,具体的返回类型,是根据实际枚举的个数决定的,如果小于等于64
,则返回RegularEnumSet
类型,否则返回JumboEnumSet
类型。这两个类型对于使用者来说,都是不可见的。如果RegularEnumSet
类型,在将来不再为小的枚举类型提供优势,即便在未来的发行版删除RegularEnumSet
也不会有什么影响。同样的,未来也可以继续添加第三个、第四个版本,对使用者也是无感的。使用者不需要关心具体返回的是什么对象,他们只知道,返回的对象都是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");
// 如果小于等于 64 ,则返回 RegularEnumSet
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
// RegularEnumSet 类是 EnumSet的子类
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
...
}
静态工厂方法与构造器比起来,在编写静态工厂方法的时候,具体的类型可以不存在。
这句话还是面向接口的优势,意思就是,我们在编写方法的时候,可以没有任何实现类,在使用的使用,先注册实现类,然后再返回实现类,这使得扩展变得很容易。我们只是在维护一个框架,一个接口,具体的实现,我们不给出,谁都可以实现,然后注册使用。这也是 服务者框架 的含义。
JDBC
就是这么一个思想的服务者框架。关于服务者框架看这里
缺点:
静态工厂方法与构造器比起来,没有
public
或者protected
修饰的构造器,无法实现继承例如,上面提到的
Collections
,就无法被继承,我们就不能继承其中的任何一个便利的实现。这或许,也是一种对使用组合而非继承的鼓励。静态工厂方法与构造器比起来,它们不容易被程序员所知晓
在文档中,它们不像构造器那么显眼,在上面单独的列出来,基于这个原因,要想知道如何实例化一个类,使用静态工厂方法比使用构造器相比,前者是比较困难的,因为文档中,静态工厂方法和其他静态方法没啥区别,没有做特殊处理。
java
文档工具或许在未来会注意到这个问题,对静态工厂方法多给予一些关注。同时,我们可以在文档中对静态工厂方法的名字做一些特殊处理,遵守常见的命名规范,来减少这个问题,比如像下面提到的几个规范:
from
,类型转换方法,根据一个单一传入的参数,返回一个对应的类型,比如:Date d = Date.from(instant);
。of
, 聚合方法,接受多个参数,返回一个合并它们的实例。比如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
valueOf
,比 from 和 of 更加详细 。比如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance or getInstance
,获取实例方法,根据传入的参数,返回实例,但是不保证返回的实例完全一样,根据传入的参数不同而不同。比如:StackWalker luke = StackWalker.getInstance(options);
。create or newInstance
,获取新的实例,每次都返回新创建的实例。比如:Object newArray = Array.newInstance(classObject, arrayLen);
。getType
,和getInstance
类似,用于返回的类型,不是静态工厂方法所在的类,而是其他类型。比如:FileStore fs = Files.getFileStore(path);
。newType
和newInstance
类型,同样用于返回的类型,不是静态工厂方法所在的类,而是其他类型。比如:BufferedReader br = Files.newBufferedReader(path);
。type
,getType and newType
的简化版。比如:List<Complaint> litany = Collections.list(legacyLitany);
。
总结下,静态工厂方法和 public
构造器都有自己的优点,了解它们各自的优点是有帮助的。大部分情况下,静态工厂方法更占优势,所以,我们应该避免第一反应就使用构造器,而是先考虑下静态工厂方法。
这里的静态工厂方法,和设计模式中的静态工厂模式,很相似,但是设计模式中的静态工厂模式,它是对外隐藏对象的实现细节,通过一个工厂,根据不同的输入,产生不同的输出。本条目中的工厂,只会产生特定的输出,即自己的实例。二者还是不同的。 ↩︎
方法签名,指的是方法名字,以及方法参数列表,包括方法参数的顺序。 ↩︎
类似于单例模式的饿汉式 ↩︎
类似于单例模式的懒汉式 ↩︎
享元模式,23种设计模式中的一种, 它针对每一种内部状态仅提供一个对象,设置不同的外部状态,产生多种不同的形态,但是其内部状态对象只有一个,达到对象复用的目的。 ↩︎
这里的
Tyle
类型,不是泛型的Type
,只是一种代指,跟xxx
一个意思,表示1.7
以前,对于面向接口编程的时候,想要返回协变类型,常规的做法,是写一个不可实现类 ,类的名字,就是接口的名字,多加一个s
。 ↩︎
effective java 3th item1:考虑静态工厂方法代替构造器的更多相关文章
- Effective Java 读书笔记(一):使用静态工厂方法代替构造器
这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...
- 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器
类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...
- Effective java读书札记第一条之 考虑用静态工厂方法取代构造器
对于类而言,为了让client获取它自身的一个实例,最经常使用的方法就是提供一个共同拥有的构造器. 另一种放你发,也应该子每一个程序猿的工具箱中占有一席之地.类能够提供一个共同拥有的静态 工厂方法.它 ...
- 【读书笔记 - Effective Java】01. 考虑用静态工厂方法代替构造器
获取类的实例有两种方法: 1. 提供一个公有的构造器(最常用). 2. 提供一个公有的静态工厂方法(static factory method). // 静态工厂方法示例 public static ...
- Java - 用静态工厂方法代替构造器
Effective Item - 考虑用静态工厂方法代替构造器我们有两种常见的方法获得一个类的实例: 公有的构造器 提供静态工厂方法(static factory method) 相对公有的构造器,静 ...
- 改善JAVA代码01:考虑静态工厂方法代替构造器
前言 系列文章:[传送门] 每次开始新的一本书,我都会很开心.新书新心情. 正文 静态工厂方法代替构造器 说起这个,好多可以念叨的.做了一年多的项目,慢慢也有感触. 说起构造器 大家很明白,构造器 ...
- 高效JAVA之用静态工厂方法代替构造器
程序员这行干的久了,总会染上一些恶习,我就染上一个让人深恶痛绝,自己却津津乐道的习惯,还不想改的那种,它可以叫做强迫症,也可以叫做洁癖.那就是我不允许我的IDEA出现一点点警告,什么黄色背景,绿色波浪 ...
- 静态工厂方法VS构造器
我之前已经介绍过关于构建者模式(Builder Pattern)的一些内容,它是一种很有用的模式用于实例化包含几个属性(可选的)的类,带来的好处是更容易读.写及维护客户端代码.今天,我将继续介绍对象创 ...
- Tips1:考虑用静态工厂方法代替构造器
用静态工厂方法来代替构造器为外界提供对象 描述: 静态工厂方法代替构造器来给外界提供对象,创建对象依然是由构造器来完成的 创建对象和提供对象: 创建对象的方式: 构造器 提供对象来哦方式: 构造器 类 ...
随机推荐
- Linux平台 Oracle 19c RAC安装Part2:GI配置
三.GI(Grid Infrastructure)安装 3.1 解压GI的安装包 3.2 安装配置Xmanager软件 3.3 共享存储LUN的赋权 3.4 使用Xmanager图形化界面配置GI 3 ...
- 深入理解HashMap(jdk7)
HashMap的结构图示 jdk1.7的HashMap采用数组+单链表实现,尽管定义了hash函数来避免冲突,但因为数组长度有限,还是会出现两个不同的Key经过计算后在数组中的位置一样,1.7版本 ...
- redis分布式锁&队列应用
分布式锁 setnx(set if not exists) 如果设值成功则证明上锁成功,然后再调用del指令释放. // 这里的冒号:就是一个普通的字符,没特别含义,它可以是任意其它字符,不要误解 & ...
- gdb调试和编译后运行结果不一致
今天在看代码时,遇到这么一段代码,我但是用g++编译了,运行发现有Segmentation fault. 然后就用gdb跟进去看看,可是gdb却正常执行了.不知道什么原因. #include < ...
- n的阶乘 -牛客
题目描述 输入一个整数n,输出n的阶乘(每组测试用例可能包含多组数据,请注意处理) 输入描述: 一个整数n(1<=n<=20) 输出描述: n的阶乘 解题思路 采用递归求解,也可以使用循环 ...
- Linux系统上安装OpenOffice
项目需求需要在linux上安装openOffice,本以为很简单,现在看来还是入了很多坑.理清楚就好了. 官网地址 http://download.openoffice.org/other.html ...
- ArrayList用法整理
System.Collections.ArrayList类是一个特殊的数组.通过添加和删除元素,就可以动态改变数组的长度. 一.优点 1.支持自动改变大小的功能 2.可以灵活的插入元素 3.可以灵活的 ...
- 并查集模板题----P3367 【模板】并查集
题目描述 如题,现在有一个并查集,你需要完成合并和查询操作. 输入格式 第一行包含两个整数N.M,表示共有N个元素和M个操作. 接下来M行,每行包含三个整数Zi.Xi.Yi 当Zi=1时,将Xi与Yi ...
- 11.源码分析---SOFARPC数据透传是实现的?
先把栗子放上,让大家方便测试用: Service端 public static void main(String[] args) { ServerConfig serverConfig = new S ...
- 从零写一个编译器(十三):代码生成之遍历AST
项目的完整代码在 C2j-Compiler 前言 在上一篇完成对JVM指令的生成,下面就可以真正进入代码生成部分了.通常现代编译器都是先把生成IR,再经过代码优化等等,最后才编译成目标平台代码.但是时 ...