龙空技术网

effective java 3 第2章 [1] 用静态工厂方法代替构造器

CC躺平混吃 81

前言:

此刻各位老铁们对“java中构造器和方法的区别是什么”大体比较注重,我们都需要分析一些“java中构造器和方法的区别是什么”的相关资讯。那么小编同时在网上汇集了一些有关“java中构造器和方法的区别是什么””的相关资讯,希望你们能喜欢,小伙伴们一起来学习一下吧!

原文内容:

本章的主题是创建和销毁对象:何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。

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

对于类而言,为了让客户端获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。还有一个方法,也应该在每个程序员的工具箱中占有一席之地。类可以提供一个公有的静态工厂方法(static factory method),它只是一个返回类的实例的静态方法。下面是一个来自Boolean (基本类型boolean 的装箱类)的简单示例。这个方法将boolean 基本类型值转换成了一个Boolean对象引用:

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

注意,静态工厂方法与设计模式[Gamma95] 中的工厂方法(Factory Method)模式不同。本条目中所指的静态工厂方法并不直接对应于设计模式(Design Pattern)中的工厂方法。

如果不通过公有的构造器,或者说除了公有的构造器之外,类还可以给它的客户端提供静态工厂方法。提供静态工厂方法而不是公有的构造器,这样做既有优势,也有劣势。

静态工厂方法与构造器不同的第一大优势在于,它们有名称。如果构造器的参数本身没有明确地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户端代码也更易于阅读。例如,构造器BigInteger(int ,int ,Random) 返回的BigInteger 可能为素数,如果用名为BigInteger.probablePrime的静态工厂方法来表示,显然更为清楚。(Java 4 版本中增加了这个方法。)

一个类只能有一个带有指定签名的构造器。编程人员通常知道如何避开这一限制:通过提供两个构造器,它们的参数列表只在参数类型的顺序上有所不同。实际上这并不是个好主意。面对这样的API,用户永远也记不住该用哪个构造器,结果常常会调用错误的构造器。并且在读到使用了这些构造器的代码时如果没有参考类的文档,往往不知所云。

由于静态工厂方法有名称,所以它们不受上述限制。当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且仔细地选择名称以便突出静态工厂方法直接的区别。

静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象。这使得不可变类(详见第17条)可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。Boolean.valueOf(boolean) 方法说明了这项技术:它从来不创建对象。这种方法类似于享元(Flyweight)模式[Gamma95] 。如果程序经常请求创建相同的对象,并且创建对象的代价很高,则这项技术可以极大地提升性能。

静态工厂方法能够为重复的调用返回相同对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在。这种类被称作实例受控的类(instance-controlled)。编写实例受控的类有几个原因。实例受控使得类可以确保它是一个Singleton(详见第3条)或者是不可实例化的(详见第4条)。它还使得不可变的值类(详见第17条)可以确保不会存在两个相等的实例,即当且仅当a==b 时,a.equals(b) 才为true。这是享元模式[Gamma95] 的基础。枚举(enum)类型(详见34条)保证了这一点。

静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。这样我们在选择返回对象的类时就有了更大的灵活性。

这种灵活性的一种应用是,API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API 变得非常简洁。这项技术适用于基于接口的框架(interface-based framework)(详见第20条),因为在这种框架中,接口为静态工厂方法提供了自然返回类型。

在Java8之前,接口不能有静态方法,因此按照惯例,接口Type的静态工厂方法被放在一个名为Types 的不可实例化的伴生类(详见第4条)中。例如,Java Collection Framework的集合接口有45个工具实现,分别提供了不可修改的集合、同步集合,等等。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象的类都是非公有的。

现在的Collections Framework API 比导出45个独立公有类的那种实现方式要小得多,每种便利实现都对应一个类。这不仅仅是指API 数量上的减少,也是概念意义上的减少:为了使用这个API,用户必须掌握的概念在数量和难度上都减少了。程序员知道,被返回的对象是由相关的接口精确指定的 ,所以他们不需要阅读有关的类文档。此外,使用这种静态工厂方法时,甚至要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象,这是一种良好的习惯(详见第64条)。

从Java8版本开始,接口中不能包含静态方法的这一限制成为历史,因此一般没有任何理由给接口提供一个不可实例化的伴生类。已经被放在这种类中的许多公有的静态成员,应该被放到接口中去。但是要注意,仍然有必要将这些静态方法背后的大部分实现代码,单独放进一个包级私有的类中。这是因为在Java 8 中仍要求接口的所有静态成员都必须是公有的。在Java 9 中允许接口有私有的静态方法,但是静态域和静态成员类仍然需要是公有的。

静态工厂的第四大优势在于,所返回的对象的类可以随着每次调用而变化,这取决于静态工厂方法的参数值。只要是已声明的返回类型的子类型,都是允许的。返回对象的类也可能随着发行版本的不同而不同。

EnumSet(详见第36条)没有公有的构造器,只有静态工厂方法。在OpenJDK实现中,它们返回两种子类之一的一个实例,具体则取决于底层枚举类型的大小:如果它的元素有64个或更少,就像大多数枚举一样,静态工厂方法就会返回一个RegularEnumSet实例,用单个long进行支持;如果枚举类型有65个或更多,工厂就返回JumboEnumSet实例,用一个long 数组进行支持。

这两个实现类的存在对于客户端来说是不可见的。如果RegularEnumSet不能再给小的枚举类型提供性能优势,就可能从未来的发行版本中将它删除,不会造成任何负面的影响。同样的,如果事实证明对性能有好处,也可能在未来的发行版本中添加第三甚至第四个EnumSet 实现。客户端永远不知道也不关心它们从工厂方法中得到的对象的类,它们只关心它是EnumSet的某个子类。

静态工厂的第五大优势在于,方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如JDBC(Java数据库连接)API。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。

服务提供者框架中有三个重要的组件:服务接口(Service Interface),这是提供者实现的;提供者注册API(Provider Registration API),这是提供者用来注册实现的;服务访问API(Service Access API),这是客户端用来获取服务的实例。服务访问API是客户端用来指定某种选择实现的条件。如果没有这样的规定,API 就会返回默认实现的一个实例,或者允许客户端遍历所有可用的实现。服务访问API是“灵活的静态工厂”,它构成了服务提供者框架的基础。

服务提供者框架的第四个组件 - 服务提供者接口(Service Provider Interface)是可选的,它表示产生服务接口之实例的工厂对象。如果没有服务提供者接口,实现就通过反射方式进行实例化(详见第65条)。对于JDBC来说,Connection 就是其服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。

服务提供者框架模式有着无数种变体。例如,服务访问API可以返回比提供者需要的更丰富的服务接口。这就是桥接(Bridge)模式。依赖注入框架(详见第5条)可以被看作是一个强大的服务提供者。从Java6版本开始,Java平台就提供了一个通用的服务提供者框架java.util.ServiceLoader,因此你不需要(一般来说也不应该)再自己编写了(详见第59)。JDBC 不用ServiceLoader,因为前者出现得比后者早。

静态工厂方法的主要缺点在于,类如果不含公有的或者受保护的构造器,就不能被子类化。例如,要想将Collections Framework 中的任何便利的实现类子类化,这是不可能的。但是这样也许会因祸得福,因为它鼓励程序员使用复合(composition),而不是继承(详见第18条),这正是不可变类型所需要的(详见第17条)。

静态工厂方法的第二个缺点在于,程序员很难发现它们。在API 文档中,它们没有像构造器那样在API文档中明确标识出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类是非常困难的。Javadoc 工具总有一天会注意到静态工厂方法。同时通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以弥补这一劣势。下面是静态工厂方法的一些惯用名称。这里只列出了其中的一部分:

from——类型转换方法,它只有单个参数,返回该类型的一个相对应的实例,例如:Date d =Date.from(instance);of——聚合方法,带有多个参数,返回该类型的一个实例,把它们合并起来,例如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);valueOf——比from和of更繁琐的一种替代方法,例如: BigInteger prime = BigInteger.value(Integer.MAX_VALUE);instance 或者 getInstance —— 返回的实例是通过方法的(如有)参数来描述的,但是不能说与参数具有同样的值,例如: StackWalker luke = StackWalker.getInstance(options);create 或者 newInstance —— 像 instance 或 getInstance 一样,但create 或 newInstance 能够确保每次调用都返回一个新的实例,例如: Object newArray = Array.newInstance(calssObject,arrayLen);getType —— 像 getInstance 一样,但是在工厂方法处于不同的类中的时候使用。Type 表示工厂方法所返回的对象类型,例如: FileStore fs = Files.getFileStore(path);newType—— 像newInstance 一样,但是在工厂方法处于不同的类中的时候使用。Type 表示工厂方法所返回的对象类型,例如: BufferedReader br = Files.newBufferedReader(path);type—— getType 和 newType 的简版,例如: List<Complaint> litany = Collections.list(legacyLitany);

简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。静态工厂方法经常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。

个人补充

java 创建类的实例几种方式

最基础的 new 关键字反射序列化 - 反序列化克隆

本条目中说到的静态工厂方法不是独立的创建对象方式,它内部实际上还是用构造器或者别的方式来创建了对象。

所以,本质上,静态工厂方法不是真的代替了构造器,而是把构造器封装起来,以便执行一些其他的操作。就像美团一样,他一碗菜都不炒,只是做了一个中间商

标签: #java中构造器和方法的区别是什么