Effective Java - 第一条:用静态工厂方法代替构造器

第二章 创建和销毁对象

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

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

        创建一个类的实例,可以选择提供一个公有的构造器,或者选择在类中提供一个公有的静态方法。

        注意:静态工厂方法与设计模式中的工厂方法模式不同。本条目所指的静态工厂方法并不直接对应于设计模式中的工厂方法。

提供静态工厂方法而不是公有的构造器,这样做既有优势,也有劣势。

优势
  • 优势1:它们有名称
                当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且仔细地选择名称以便突出静态工厂方法之间的区别。
  • 优势2:不必在每次调用它们的时候都创建一个新对象
                这使得不可变类可以预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的对象。类似于享元模式。

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

  • 优势3:它们可以返回类型的任何子类型的对象
                这种更大的灵活性的一种应用是,API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁。这项技术适用于基于接口的框架,因为在这种框架中,接口为静态工厂提供了自然返回类型。
                在Java 8 之前,接口不能有静态方法,因此按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化的伴生类中。例如,Java Collections Framework 的集合接口有45个工具实现,分别提供了不可修改的集合、同步集合,等等。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象的类都是非公有的。
                现在的Collections Framework API比导出45个独立公有类的那种实现方式要小得多,每种便利实现都对应一个类。这不仅仅是指API数量上的减少,也是概念意义上的减少:为了使用这个API,用户必须掌握的概念在数量和难度上都减少了。程序员知道,被返回的对象是由相关的接口精确指定的,所以他们不需要阅读有关的文档。此外,使用这种静态工厂方法时,甚至要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象,这是一种良好的习惯。
                从Java 8 版本开始,接口中不能包含静态方法的这一限制成为历史,因此一般没有任何理由给接口提供一个不可实例化的伴生类。已经被放在这种类中的许多公有的静态成员,应该被放到接口中去。但是要注意,仍然有必要将这些静态方法背后的大部分实现代码,单独放进一个包级私有的类中。这是因为在Java 8 中仍要求接口的所有静态成员都必须是公有的。在Java 9 中允许接口有私有的静态方法,但是静态域和静态成员仍然需要是公有的。

  • 优势4:返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值
                只要是已声明的返回类型的子类型,都是允许的。返回对象的类也可能随着发型版本的不同而不同。客户端永远不知道也不关心它们从工厂方法中得到的对象的类,它们只关心它是EnumSet的某个子类。

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

            服务提供者框架中有三个重要的组件:服务接口,这是提供者实现的;提供者注册API,这是提供者用来注册实现的;服务访问API这是客户端用来获取服务的实例。服务访问API是客户端用来指定某种选择实现的条件。如果没有这样的规定,API就会返回默认实现的一个实例,或者允许客户端遍历所有可用的实现。服务访问API是“灵活的静态工厂”,它构成了服务提供者框架的基础。
            服务提供者框架的第四个组件服务提供者接口是可选的,它表示产生服务接口之实例的工厂对象。如果没有服务提供者接口,实现就通过反射方式进行实例化。对于JDBC来说,Connection就是器服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。
            服务提供者框架模式有着无数种变体。例如,服务访问API可以返回比提供者需要的更丰富的服务接口。这就是桥接模式。依赖注入框架可以被看作是一个强大的服务提供者。从Java 6 版本开始,Java平台就提供了一个通用的服务提供者框架java.util.ServiceLoader, 因此你不需要(一般来说也不应该)再自己编写了。JDBC不用ServiceLoader,因为前者出现得比后者早。

缺点
  • 缺点1:类如果不含公有的或者受保护的构造器,就不能被子类化
                例如,想要将Collections Framework 中的任何便利的实现类子类化,这是不可能的。但是这样也许会因祸得福,因为它鼓励程序员使用复合,而不是继承,这正是不可变类型所需要的。
  • 缺点2:程序员很难发现它们
                在API文档中,它们没有像构造器那样在API文档中明确标识出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类是非常困难的。Javadoc工具总有一天会注意到静态工厂方法。同时,通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以弥补这一劣势。下面是静态工厂方法的一些惯用名称。这里之列出了一小部分。

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


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值