effective java笔记--第一条 用静态工厂方法代替构造器

1. 考虑用静态工厂方法代替构造器

 对于类而言,为了让客户端获取它自身的一个实例,最常用的方法就是提供一个公有的构造器。

 另外,类可以提供一个公有的静态工厂方法(static factory method),它只是一个返回类的实例的静态方法

 如:Boolean 的简单示例:

      public static Boolean valueOf(boolean b) {

          return b ? Boolean.TRUE : Boolean.FALSE;

          //其中Boolean.TRUE 为 public static final Boolean TRUE = new Boolean(true); 即为该Boolean 类型的实例

      }

 什么是静态工厂方法?(可参考:https://www.cnblogs.com/dyj-blog/p/8867028.html)

effective java笔记--第一条 用静态工厂方法代替构造器

静态工厂方法相对于构造器的优势在于:

1)静态工厂方法有名称

   如果构造器的参数本身没有确切的描述正被返回的对象,具有适当名称的静态工厂会更容易使用。

    一个类只能有一个带有指定签名的构造器。编程人员通常知道如何避开这一限制:通过提供两个构造器,它们的参数列表只在参数类型的顺序上有所不同。面对这样的API,用户永远也记不住该用哪个构造器,结果常常会常调用错误的构造器。而且可读性很差。

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

2)不必在每次调用它们的时候都创建一个新对象

    使得不可变类(实例不能被修改的类,如:String)可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。Boolean.valueOf(boolean) 说明了这项技术: 它从来不创建对象。如果程序经常请求创建相同的对象,并且创建对象的代价很高,则这项技术可以极大的提高性能。

静态工厂方法能够为重复的调用返回相同对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在。这种类被称作实例受控的类(instance-controlled)。

    编写实例受控的类有几个原因:使得类可以确保它是一个Singleton或者是不可实例化的(如:Math);使得不可变的类可以确保不会存在两个相等的实例,即当且仅当a==b的时候才有a.equals(b)为true。如果类保证了这一点,它的客户端就可以使用==操作符来代替equals(Object)方法,这样可以提高性能。枚举(enum)类型保证了这一点。

3)可以返回原返回类型的任何子类型的对象。

     这样我们在选择返回对象的类时就有了更大的灵活性。

     这种灵活性的一种应用是,API可以返回对象,同事又不会使对象的类变成公有的。

     以这种方式隐藏实现类会使API变得非常简洁。这项技术适用于基于接口的框架(interface-based framework)(见20条),

     因为在这种框架中,接口为静态工厂方法提供了自然返回类型。

 effective java笔记--第一条 用静态工厂方法代替构造器

在 Java8之前,接口不能有静态方法,因此按照管理,接口Type的静态工厂方法被放在一个名为 Types 的不可实例化的

伴生类中(我觉得可以参照: Collection 和 Collections。java.util.Collection 是一个集合接口,提供了对集合对象进行基本操作的

通用接口方法。而java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化就像一个

工具类,服务于Java的Collection框架)。例如,Java Collections Framework 的集合接口有45个工具实现,分别提供了不可修改

的集合(不可变集合对象只能访问不能修改, 如emptyXxx() :返回一个空的不可变的集合对象;singletonXxx() :返回一个只包含

指定对象的,不可变的集合对象;unmodifiableXxx() :返回指定集合对象的不可变视图)、同步集合(synchronizedXxx方法)。

几乎所有这些实现都是通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象的类都是非公有的。

4)所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值

     只要是已声明的返回类型的子类型,都是允许的。返回对象的类也可能随着发行版本的不同而不同。

      EnumSet 没有公有的构造器,只有静态工厂方法。在OpenJDK 实现中,它们返回两种自雷之一的一个实例,具体则取决于

       底层枚举类型的大小:如果它的元素有64个或者更少,就像大多数枚举类型一样,静态工厂方法就返回一个 

       RegalarEnumSet 实例,用单个 long 进行支持;如果枚举类型有 65个或者更多元素,工厂就返回 JumboEnumSet实例,用

       一个long数组进行支持。

      effective java笔记--第一条 用静态工厂方法代替构造器

5) 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

    这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如 JDBC(Java 数据库连接)API。

    服务提供者框架:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来.

    服务提供者框架中有三个重要的组件:

       1)服务接口(Service Interface):提供者实现的

       2)提供者注册 API(Provider Registration API):提供者用来注册实现的

       3)服务访问API(Service Access API):客户端用来获取服务的实例

            (服务访问 API 是客户端用来指定某种选择实现的条件。如果没有这样的规定,API就会返回默认实现的一个实例,

              或者允许客户端遍历所有可用的实现。服务访问API是"灵活的静态工厂",它构成了服务提供者框架的基础。)

       4)服务提供者接口(Service Provider Interface):产生服务接口之实例的工厂对象(可选)。如果没有服务提供者接口,

             实现就通过反射方式进行实例化。

      对于 JDBC 来说,Connection 就是其服务接口的一部分,DriverManager.registerDriver 是提供者注册API,

      DriverManager.getConnection 是服务访问API,Driver 是服务提供者接口。

     (Connection接口就是一个服务接口,定义了很多操作,但是JDBC本身不对该服务进行实现,而是由MySQL,sqlServer、Oracle、DB2等各数据库厂商去实现。然后注册到DriverManager中。 用户只需要根据注册时的KEY 去查找到相关的服务即可)

      Java6开始,java平台就提供了一个通用的服务提供者框架 java.util.ServiceLoader,因此不需要再自己编写了。

静态工厂方法的主要缺点在于:

        1)类如果不含公有的或者受保护的构造器,就不能被子类化。如:想将Collections Framework中的任何便利的实现类子类

             化,是不可能的。

        2)程序员很难发现他们。静态工厂方法的一些惯用名称,这里只列出了其中的一小部分:

               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 或 getInstance ---- 返回的实例是通过方法的(如有)参数来描述的,但是不能说与参数具有同样的值,

                             如:StackWalker luke = StackWalker.getInstance(options);

               create 或 newInstance ---- 像 instance 或 getInstance 一样,但create或者newInstance能够确保每次调用都返回一个

                            新的实例,如:Object newArray = Array.newInstance(classObject, 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);