【Effective Java 第三版】Item 1:考虑静态工厂方法而不是构造函数

前言:Effective Java第三版涵盖了Java 7、Java 8、Java 9中语言和库的各种新特性,目前还没有中文版,以下内容全是本人的手工翻译,虽费时费力,但也算是读书笔记了,有什么不足的地方,欢迎大家指出!

第二章 创建和销毁对象
正文:本章讲述创建和销毁对象:什么时候创建和如何创建对象,什么时候避免创建和如何避免创建对象,如何确保对象被及时销毁,以及如何管理必须先于销毁之前的清理动作。

Item 1:考虑静态工厂方法而不是构造函数

类允许客户端获取实例的传统方式是提供公共构造函数。还有另一种技术应该成为每个程序员工具包的一部分。类可以提供公共静态工厂方法,它只是一个返回类实例的静态方法。这是Boolean(boolean的包装类)的一个简单示例。此方法将布尔原始值转换为布尔对象引用。

public static Boolean valueOf(boolean b){
        return b ? Boolean.TRUE : Boolean.FALSE;
    }

请注意,静态工厂方法与设计模式中的工厂方法模式不同。此项中描述的静态工厂方法不等同于设计模式中的。
除了公共构造函数之外,类还可以为其客户端客户端提供静态工厂方法。提供静态工厂方法有利也有弊。

静态工厂方法的一个优点是,与构造函数不同,它们有名称。
如果构造函数的参数本身没有描述返回的对象,那么具有良好命名的静态工厂更易于使用,并且生成的客户端代码更易于阅读。例如,构造函数BigInteger(int,int,Random)返回一个可能是素数的BigInteger,可以更好地表示为名为BigInteger.probablePrime的静态工厂方法。

一个类只能有一个具有给定签名的构造函数。众所周知,程序员通过提供两个构造函数来解决这个限制,这两个构造函数的参数列表仅按参数类型的顺序不同。这是一个非常糟糕的主意。这样的API的用户将永远无法记住哪个构造函数是哪个,并且最终会错误地调用错误的构造函数。在不参考类文档的情况下,阅读使用这些构造函数的代码的人不会知道代码的作用。

因为静态工厂方法有名称,所以它们不受前一段中讨论的限制。如果一个类似乎需要多个具有相同签名的构造函数,请使用静态工厂方法替换构造函数,并使用精心选择的名称来突出显示它们之间的差异。

静态工厂方法的第二个优点是,与构造函数不同,它们不需要在每次调用时创建新对象。
这允许不可变类使用预构建的实例,或者在构造它们时缓存实例,并重复分配它们以避免创建不必要的重复对象。Boolean.valueOf(boolean)方法说明了这点:它永远不会创造一个对象。这种技术类似于Flyweight(享元)模式。如果经常请求等效对象,它可以大大提高性能,特别是如果它们创建起来开销大。

静态工厂方法的重复调用返回相同对象的能力,允许类在任何时候保持对存在的实例的严格控制。这样做的类被认为是实例控制的。编写实例控制类有几个原因。实例控件允许类保证它是单例或不可实例化的。此外,它允许值不可变的类保证不存在两个相等的实例:当且仅当“a == b”时,a.equals(b)。这是享元模式的基础。枚举类型提供此保证。

静态工厂方法的第三个优点是,与构造函数不同,它们可以返回其返回类型的任何子类型的对象。这使您在选择返回对象的类时具有很大的灵活性。

这种灵活性的一个应用是API可以在不公开其类的情况下返回对象。以这种方式隐藏实现类会导致非常紧凑的API。这种技术适用于基于接口的框架,其中接口为静态工厂方法提供自然返回类型。

在Java 8之前,接口不能有静态方法。按照惯例,名为Type的接口的静态工厂方法放在名为Types的不可实例化的伴随类中。Java集合框架的接口有45个实用程序实现,提供不可修改的集合,同步集合等。几乎所有这些实现都是通过静态工厂方法在一个不可实例化的类中输出的。返回对象的类都是非公共的。集合框架API比它导出45个单独的公共类要小得多,而且每个都便于实现。它不仅仅是减少了API的大部分,而是概念上的重量:程序员为了使用API而必须掌握的概念的数量和难度。程序员知道返回的对象具有其接口指定的API,因此不需要为实现类读取其他类文档。此外,使用这种静态工厂方法要求客户端通过接口而不是实现类来引用返回的对象,这通常是一种很好的做法。
从Java 8开始,消除了接口不能包含静态方法的限制,因此通常没有理由为接口提供不可实例化的伴随类。许多公共静态成员应该放在接口本身中。但是,请注意,可能仍然需要将大量实现代码放在这些静态方法后面的单独的包私有类中。这是因为Java 8要求接口的所有静态成员都要是公共的。Java 9允许私有的静态方法,但是静态字段和静态成员类仍然要求是公共的。

静态工厂的第四个优点是,作为输入参数的函数,返回对象的类可以在调用之间变化。声明的返回类型的任何子类型都是允许的。返回对象的类也可能因发行版而异。
EnumSet类没有公共构造函数,只有静态工厂。在OpenJDK实现中,它们返回两个子类之一的实例,具体取决于底层枚举类型的大小:如果它具有64个或更少的元素,就像大多数枚举类型那样,静态工厂返回一个由一个long支持的RegularEnumSet实例;如果枚举类型有65个或更多元素,则工厂返回一个由long数组支持的JumboEnumSet实例。
这两个实现类的存在对客户端是不可见的。如果RegularEnumSet不再为小枚举类型提供性能优势,它可以从未来的版本中消除,没有任何不良影响。类似地,未来版本可以添加EnumSet的第三个或第四个实现,如果它被证明有利于性能。客户既不知道也不关心他们从工厂回来的对象的类别; 他们只关心它是EnumSet的一些子类。

静态工厂的第五个优点是,当写入包含该方法的类时,返回对象的类不需要存在。这种灵活的静态工厂方法构成了服务提供者框架的基础,如Java数据库连接API(JDBC)。服务提供者框架是提供者实现服务的系统,系统使实现可用于客户端,将客户端与实现分离。
服务提供者框架中有三个基本组件:服务接口,表示实现;提供者注册API,提供者用于注册实现;以及服务访问API,客户端用于获取服务实例。服务访问API可以允许客户端指定用于选择实现的标准。在没有这样的标准的情况下,API返回默认实现的实例,或允许客户端循环遍历所有可用的实现。服务访问API是灵活的静态工厂,它构成了服务提供者框架的基础。服务提供者框架的可选第四个组件是服务提供者接口,它描述了生成服务接口实例的工厂对象。在没有服务提供者接口的情况下,必须反复实例化实现。在JDBC的情况下,Connection扮演服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。
服务提供者框架模式有许多变体。比如,服务访问API可以向客户端返回比提供者提供的服务接口更丰富的服务接口。这是桥模式。依赖注入框架可以被视为强大的服务提供者。从Java 6开始,该平台包含一个通用服务提供程序框架java.util.ServiceLoader,因此您不需要,而且通常不应该编写自己的。JDBC不使用ServiceLoader,因为前者早于后者。

仅提供静态工厂方法的主要限制是没有公共或受保护构造函数的类不能被继承。无法在集合框架中继承任何快速实现类。可以说这可能是一种变相的好处,因为它鼓励程序员使用组合而不是继承,并且是不可变类型所必需的。

静态工厂方法的第二个缺点就是程序员很难找到它们。它们不像构造函数那样在API文档中突出,因此很难弄清楚如何实例化提供静态工厂方法而不是构造函数的类。Javadoc工具有一天可能会引起对静态工厂方法的关注。与此同时,您可以通过引起对类或接口文档中的静态工厂的关注并遵守常见的命名约定来减少此问题。以下是静态工厂方法的一些常用名称。这个列表只是一部分。

  • from——一种类型转换方法,它接受单个参数并返回此类型的相应实例。例如:
Date d = Date.from(instant);
  • of——一种聚合方法,它接受多个参数并返回包含它们的此类型的实例。例如:
Set faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf——from和of的替换选择。例如:
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance 或 newInstance——返回由其参数(如果有)描述的实例,但不能说具有相同的值。例如:
StackWalker luke = StackWalker.getInstance(options);
  • create 或 newInstance——像instance或getInstance,除了该方法保证每个调用返回一个新实例。
Object newArray = Array.newInstance(classObject, ArrayLen);
  • getType——像getInstance,但如果工厂方法属于不同的类,则使用。类型是工厂方法返回的对象类型。例如:
FileStore fs = Files.getFileStore(path);
  • newType——像newInstance,但如果工厂方法属于不同的类,则使用。类型是工厂方法返回的对象类型。例如:
BufferedReader br = Files.newBufferedReader(path);
  • type——getType和newType的简明替代方案。例如:
List litany = Collections.list(legacyLitany);

总之,静态工厂方法和公共构造函数都有它们的用途,理解它们的相对优点是值得的。通常静态工厂是优选的,所以在没有首先考虑静态工厂的情况下,避免直接提供公共构造函数。

你可能感兴趣的:(Java,Chapter2,Effective,Java)