admin管理员组

文章数量:1565849

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

对于类而言,为了让客户端获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。还有一种方法,也应在在程序员的工具箱中占有一席之地。类可以提供一个公有的 静态工厂方法 ,它只是一个返回类的实例的静态方法。


下面是一个来自 Boolean 的简单实例:这个方法将 boolean基本类型转化成 Boolean 包装类型


    public static final Boolean TRUE = new Boolean(true);

    public static final Boolean FALSE = new Boolean(false);

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

注意:

静态工厂方法与设计模式中的工厂方法模式不同,此处所指的静态工厂方法并不直接对应于设计模式中的工厂方法中的工厂方法
静态工厂方法的优势:
①静态工厂方法与构造方法的第一大优势在于:静态工厂方法含有方法名。

通过构造器的参数本身没有确切的描述被返回的对象,那么具有适当名称的静态工厂方法更容易使用,产生的客户端代码也容易阅读。


例如:构造器 BigInteger (int , int , Random)返回的 BigInteger 可能为素数,如果用名为 BigInteger.probablePrime 的静态工厂方法来表示,显然更为清楚。

    public static BigInteger probablePrime(int bitLength, Random rnd) {
        if (bitLength < 2)
            throw new ArithmeticException("bitLength < 2");

        return (bitLength < SMALL_PRIME_THRESHOLD ?
                smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
                largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
    }

一个类只能有一个带有签名的构造器。我们在编程的时候知道如何避开这一限制:那就是写两个构造器,他们的参数列表只在顺序上有所不同就可以。实际上这并不是一个好主意。面对这样的 API 用户永远也不知道该调用哪一个构造器,结果往往会调用错误的构造器。
由于静态工厂方法有名称,所以他不受上述限制。当一个类需要有多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且仔细选择名称,以便突出静态工厂方法之间的区别。

②静态工厂方法与构造方法的第二大优势在于:不必在每次调用他们的时候都创建一个新的对象。

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


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

③静态工厂方法与构造方法的第三大优势在于:它们可以返回原返回类型的子类型对象。

这样就使得我们在选择返回对象时有了很大的灵活性。这种灵活性的一种应用是,API可以返回对象,同时又不使对象的类变成公有的,以这种方式隐藏实现类会使API变得非常简洁。


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


现在的Collections Framework API 比导出45个独立公有类的那种实现方式要小得多,每种遍历实现都对应一个类。这不仅仅是指API数量上的减少,也是概念意义上的减少。此外使用这种静态工厂方法时,甚至要求客户端通过接口来引用被返回的对象,而不是通过他的实现类来引用被返回的对象,这是一种良好的习惯。


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

④静态工厂方法与构造方法的第四大优势在于:所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。

只要是已声明的返回类型的子类型,都是允许的。EnumSet 没有公有的构造器,只有静态工厂方法。它们返回两种子类之一的一个实例,具体取决于底层枚举类型的大小;如果元素小于等于64,返回RegularEnumSet对象,大于64就返回JumboEnumSet对象。

⑤静态工厂方法与构造方法的第五大优势在于:方法返回对象所属的类,在编写包含该静态工厂方法的类时可以不存在。

这种灵活的静态工厂方法构成了服务提供者框架的基础,例如:JDBC API。对于JDBC来说,Connection就是服务接口的一部分,DriverManager.registerDriver就是提供注册的API,DriverManager.getConnection就是服务访问API,Driver就是服务提供者接口。

静态工厂方法的劣势:
①静态工厂方法与构造方法的第一个劣势在于:类如果不含公有的或者受保护的构造器,就不能被子类化。

例如:要想将 Collections Framework 中的任何便利的实现类子类化,这是不可能的。

②静态工厂方法与构造方法的第二个劣势在于:程序员很难发现他们。

在API文档中,它们没有像构造器那样明确的标注出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要查明如何实例化一个类是非常困难的。


下面是静态工厂方法一些惯用名称。(这里只是一小部分)

  • 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 或者 getInstance ---- 返回的实例是通过方法的(如有)参数来描述的,但是不能说预期参数具有同样的值
    StackWalker luck= StackWalker.getInstance(options);

本文标签: EffectiveJava