Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。java
一个类容许客户端获取其实例的传统方式是提供一个公共构造方法。 其实还有另外一种技术应该成为每一个程序员工具箱的一部分。 一个类能够提供一个公共静态工厂方法,它只是一个返回类实例的静态方法。 下面是一个Boolean
简单的例子(boolean
基本类型的包装类)。 此方法将boolean
基本类型转换为Boolean
对象引用:程序员
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
注意,静态工厂方法与设计模式中的工厂方法模式不一样[Gamma95]。本条目中描述的静态工厂方法在设计模式中没有直接的等价。数据库
类能够为其客户端提供静态工厂方法,而不是公共构造方法。提供静态工厂方法而不是公共构造方法有优势也有缺点。设计模式
静态工厂方法的一个优势是,不像构造方法,它们是有名字的。 若是构造方法的参数自己并不描述被返回的对象,则具备精心选择名称的静态工厂更易于使用,而且生成的客户端代码更易于阅读。 例如,返回一个可能为素数的BigInteger
的构造方法BigInteger(int,int,Random)
能够更好地表示为名为BigInteger.probablePrime
的静态工厂方法。 (这个方法是在Java 1.4中添加的。)数组
一个类只能有一个给定签名的构造方法。 程序员知道经过提供两个构造方法来解决这个限制,这两个构造方法的参数列表只有它们的参数类型的顺序不一样。 这是一个很是糟糕的主意。 这样的API用户将永远不会记得哪一个构造方法是哪一个,最终会错误地调用。 阅读使用这些构造方法的代码的人只有在参考类文档的状况下才知道代码的做用。缓存
由于他们有名字,因此静态工厂方法不会受到上面讨论中的限制。在类中彷佛须要具备相同签名的多个构造方法的状况下,用静态工厂方法替换构造方法,并仔细选择名称来突出它们的差别。框架
静态工厂方法的第二个优势是,与构造方法不一样,它们不须要每次调用时都建立一个新对象。这容许不可变的类(条目17)使用预先构建的实例,或者在构造时缓存实例,并反复分配它们以免建立没必要要的重复对象。boolean.valueof(boolean)
方法说明了这种方法:它从不建立对象。这种技术相似于享元模式(Flyweight)[Gamma95]。若是常常请求等价对象,那么它能够极大地提升性能,特别是若是在建立它们很是昂贵的状况下。dom
静态工厂方法从重复调用返回相同对象的能力容许类保持在任什么时候候存在的实例的严格控制。这样作的类被称为实例控制( instance-controlled)。编写实例控制类的缘由有不少。实例控制容许一个类来保证它是一个单例(3)项或不可实例化的(条目4)。同时,它容许一个不可变的值类(条目17)保证不存在两个相同的实例:当且仅当a== b
时a.equals(b)
。这是享元模式的基础[Gamma95]。Enum
类型(条目34)提供了这个保证。工具
静态工厂方法的第三个优势是,与构造方法不一样,它们能够返回其返回类型的任何子类型的对象。 这为你在选择返回对象的类时提供了很大的灵活性。性能
这种灵活性的一个应用是API能够返回对象而不须要公开它的类。 以这种方式隐藏实现类会使 API很是紧凑。 这种技术适用于基于接口的框架(条目20),其中接口为静态工厂方法提供天然返回类型。
在Java 8以前,接口不能有静态方法。根据约定,一个名为Type
的接口的静态工厂方法被放入一个非实例化的伙伴类(companion class)(条目4)Types
类中。例如,Java集合框架有45个接口的实用工具实现,提供不可修改的集合、同步集合等等。几乎全部这些实现都是经过静态工厂方法在一个非实例类(java .util. collections
)中导出的。返回对象的类都是非公开的。
Collections
框架API的规模要比它以前输出的45个单独的公共类要小得多,每一个类有个便利类的实现。不只是API的大部分减小了,还包括概念上的权重:程序员必须掌握的概念的数量和难度,才能使用API。程序员知道返回的对象刚好有其接口指定的API,所以不须要为实现类读阅读额外的类文档。此外,使用这种静态工厂方法须要客户端经过接口而不是实现类来引用返回的对象,这一般是良好的实践(条目64)。
从Java 8开始,接口不能包含静态方法的限制被取消了,因此一般没有理由为接口提供一个不可实例化的伴随类。 不少公开的静态成员应该放在这个接口自己。 可是,请注意,将这些静态方法的大部分实现代码放在单独的包私有类中仍然是必要的。 这是由于Java 8要求全部接口的静态成员都是公共的。 Java 9容许私有静态方法,但静态属性和静态成员类仍然须要公开。
静态工厂的第四个优势是返回对象的类能够根据输入参数的不一样而不一样。 声明的返回类型的任何子类都是容许的。 返回对象的类也能够随每次发布而不一样。
EnumSet
类(条目 36)没有公共构造方法,只有静态工厂。 在OpenJDK实现中,它们根据底层枚举类型的大小返回两个子类中的一个的实例:若是大多数枚举类型具备64个或更少的元素,静态工厂将返回一个RegularEnumSet
实例, 返回一个long
类型;若是枚举类型具备六十五个或更多元素,则工厂将返回一个JumboEnumSet
实例,返回一个long
类型的数组。
这两个实现类的存在对于客户端是不可见的。 若是RegularEnumSet
再也不为小枚举类型提供性能优点,则能够在将来版本中将其淘汰,而不会产生任何不良影响。 一样,将来的版本可能会添加EnumSet
的第三个或第四个实现,若是它证实有利于性能。 客户端既不知道也不关心他们从工厂返回的对象的类别; 他们只关心它是EnumSet
的一些子类。
静态工厂的第5个优势是,在编写包含该方法的类时,返回的对象的类不须要存在。这种灵活的静态工厂方法构成了服务提供者框架的基础,好比Java数据库链接API(JDBC)。服务提供者框架是提供者实现服务的系统,而且系统使得实现对客户端可用,从而将客户端从实现中分离出来。
服务提供者框架中有三个基本组:服务接口,它表示实现;提供者注册API,提供者用来注册实现;以及服务访问API,客户端使用该API获取服务的实例。服务访问API容许客户端指定选择实现的标准。在缺乏这样的标准的状况下,API返回一个默认实现的实例,或者容许客户端经过全部可用的实现进行遍历。服务访问API是灵活的静态工厂,它构成了服务提供者框架的基础。
服务提供者框架的一个可选的第四个组件是一个服务提供者接口,它描述了一个生成服务接口实例的工厂对象。在没有服务提供者接口的状况下,必须对实现进行反射实例化(条目65)。在JDBC的状况下,Connection
扮演服务接口的一部分,DriverManager.registerDriver
提供程序注册API、DriverManager.getConnection
是服务访问API,Driver
是服务提供者接口。
服务提供者框架模式有许多变种。 例如,服务访问API能够向客户端返回比提供者提供的更丰富的服务接口。 这是桥接模式[Gamma95]。 依赖注入框架(条目5)能够被看做是强大的服务提供者。 从Java 6开始,平台包含一个通用的服务提供者框架java.util.ServiceLoader
,因此你不须要,通常也不该该本身编写(条目59)。 JDBC不使用ServiceLoader
,由于前者早于后者。
只提供静态工厂方法的主要限制是,没有公共或受保护构造方法的类不能被子类化。例如,在Collections
框架中不可能将任何方便实现类子类化。能够说,这多是塞翁失马,由于它鼓励程序员使用组合而不是继承(条目18),而且是不可变类型(条目17)。
静态工厂方法的第二个缺点是,程序员很难找到它们。它们不像构造方法那样在API文档中突出,所以很难找出如何实例化一个提供静态工厂方法而不是构造方法的类。Javadoc工具可能有一天会引发对静态工厂方法的注意。与此同时,能够经过将注意力吸引到类或接口文档中的静态工厂以及遵照通用的命名约定来减小这个问题。下面是一些静态工厂方法的经常使用名称。如下清单并不是完整:
Date d = Date.from(instant)
;Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING)
;BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE)
;StackWalker luke = StackWalker.getInstance(options)
;Object newArray = Array.newInstance(classObject, arrayLen)
;FileStore fs = Files.getFileStore(path)
;BufferedReader br = Files.newBufferedReader(path)
;List<Complaint> litany = Collections.list(legacyLitany)
;总之,静态工厂方法和公共构造方法都有它们的用途,而且了解它们的相对优势是值得的。一般,静态工厂更可取,所以避免在没有考虑静态工厂的状况下提供公共构造方法。