创建和销毁对象

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

类可以提供一个公有的静态工厂方法(public static factory method)来返回一个类的实例。
例如,Boolean类的valueOf()方法:

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

提供public静态工厂方法而不是public的构造器的优势如下:

  1. 静态工厂方法有名称,不过要慎重地选择名称
    例如,构造器BigIntger(int, int, Random)返回的BigInteger可能为素数,如果用名为BigInteger.probablePrime()的静态工厂方法显然更清晰。 
    示例: BigIntegerTest

  2. 不必在每次调用静态工厂方法的时候都创建一个新对象 
    如果程序经常经常请求创建相同的对象,并且创建对象的代码很高,则这项技术可以极大地提升性能。 
    示例:BooleanTest 
    当且仅当a==b的时候才有a.equals(b)为true, 如果类保证了这一点, 它的客户端就可以使用==操作符来代替equals(Object)方法,这样可以提升性能。

  3. 静态工厂方法可以返回子类类型的对象 
    API返回的对象对应的子类可以不是公有的,这种方式可以隐藏具体的实现类。 
    这项技术适合于基于接口的框架,接口为静态工厂提供了自然返回类型。 
    接口不能有静态方法,因此按照惯例,返回接口的静态工厂方法是放在一个不可实例化(non-instantiability)的类中。 
    示例: InterfaceCannotStaticTest 

    示例: java.util.Collections是一个不可实例化的类,它提供了不可修改的集合、同步集合的静态工厂方法。我们模仿其写了一个简单例子: MyListTest 

    通过使用这种静态工厂方法,甚至要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象,这是一种良好的习惯。 为了提升软件的可维护性和性能,返回对象的(子)类也可能随着发行版本的不同而不同。 

    示例:EnumSetTest 发行版本1.5中引入java.util.EnumSet没有公有的构造器,只有静态工厂方法,返回两种实现类之一,具体则取决于底层枚举类型的大小。

    示例:服务提供者框架

  4. 静态工厂方法在创建参数化类型实例的时候,使得代码更加简洁 
    例如,创建一个Map<String, List<String>>对象,在调用参数化类的构造器时,即使类型参数明显,也必须指明。这通常要求你连接两次提供类型参数:
    Map<String, List<String>> map = new HashMap<String, List<String>>(); 
    随着类型参数变得越来越长,越来越复杂,这一冗长的说明也很快变得痛苦起来。 
    但是有了静态方法,编译器可以替你找到类型参数,这被称作类型推导(type inference): 
    Map<String, List<String>> map = HashMap.newInstance(); 
    假设HashMap提供了这个静态工厂方法:
    public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); } 
    总有一天,Java将能够在构造器调用以及方法调用中执行这些类型推导,但到发行版本1.6为止暂时还无法这么做。 
    遗憾的是,到发行版本1.6为止,标准的集合实现如HashMap并没有工厂方法,但是可以把这些方法放在你自己的工具类中。

公有的静态工厂方法的缺点:

  1. 类如果不含有公有的或者受保护的构造器,静态工厂方法返回的实例不能被子类化。
  2. 同样地,对于非公有类(nonpublic classes),静态工厂方法返回的实例也不能被子类化。 示例: MyListTest
  3. 静态工厂方法与其它的静态方法无法被明确区分,不像构造器那样在api文档中被明确的标识出来。 所以,在类或接口注释中关注静态工厂,使用一些约定俗成的命名
惯用名称说明
valueOf Returns an instance that has, loosely speaking, the same value as its parameters. Such static factories are effectively type-conversion methods.
of A concise alternative to valueOf , popularized by EnumSet.
getInstance Returns an instance that is described by the parameters but cannot be said to have the same value. In the case of a singleton, getInstance takes no parameters and returns the sole instance.
newInstance Like getInstance , except that newInstance guarantees that each instance returned is distinct from all others.
getType Like getInstance , but used when the factory method is in a different class. Type indicates the type of object returned by the factory method.
newType Like newInstance , but used when the factory method is in a different class. Type indicates the type of object returned by the factory method.

简而言之,静态工厂方法和公有构造器都各有用处。 静态工厂方法通常更加适合,因此切忌第一反应是提供公有的构造器,而不先考虑静态工厂方法。

遇到多个构造器参数时要考虑用构建器

静态工厂和构造器有一个共同的局限性:它们都不能很好地扩展到大量的可选参数。考虑用一个类表示一个食品的营养成分表。对于这样的类,应该用哪个构造器或者静态方法来编写呢?

  1. 重叠构造器(telescoping constructor) 
    示例: telescoping_constructor/NutritionFacts 
    ① 当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。 
    ② 如果读者想知道那些值是什么意思,必须很仔细地数着这些参数来探个究竟。如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会出错,但是在程序运行时会出现错误行为。
  2. JavaBeans模式:调用一个无参构造器来创建对象,然后调用setter()方法来设置每个必要的参数,以及每个相关的可选参数。
    示例: javabeans/NutritionFacts 
    ① 无法保证一致性:JavaBean模式自身有着很严重的缺点,因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处在不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败,这种失败调试起来十分困难。 
    ② 阻止了把类做成不可变,这就需要程序员付出额外的努力来确保它的线程安全。
  3. Builder模式 
    示例:builder/NutritionFacts 
    这样的客户端代码很容易编写,而且易读。Builder模式模拟了具名的可选参数,就像Python一样。 
    对参数加强约束条件: build()方法检验或者多个setter()方法检验这些约束条件,如果该约束条件没有得到满足,就抛出IllegalStateException或IllegalArgumentException。 
    设置了参数的Builder生成了一个很好的抽象工厂(Abstract Factory)。Java中的Class对象就是一个抽象工厂,用newInstance()方法充当build()方法的一部分。 
    Builder模式也有它自身不足,为了创建对象,必须先创建它的构建器,它还会比重叠构造器模式更加冗长,因此只有在很多参数(4个以上)的时候才考虑使用Builder模式。但是要记住,将来你可能需要添加参数,如果一开始就使用构造器或者静态工厂,等到类需要多个参数时才添加构建器,就无法控制,那些过时的构造器或者静态工厂显得十分不协调。因此通常最好一开始就使用构建器。

用私有构造器或者枚举类型强化Singleton属性

Singleton指仅仅被实例化一次的类。Singleton通常被用来代表那些本质上唯一的系统组件。 
在Java 1.5发行版本之前,实现Singleton有两种方法。这两种方法都要把构造器保持为私有的,并导出公有的静态成员,以便允许客户端能够访问该类的唯一实例。 
第一种方法,公有静态成员是一个final域。示例: field/Elvis.java 
私有构造器仅被调用一次,用来实例化公有的静态域Elvis.INSTANCE。 
但要提醒一点:享有特权的客户端可以借助AccessibleObject.setAccessible()方法,通过反射机制调用私有构造器,示例:ModifyingSingleton 。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。

通过私有构造器强化不可实例化的能力

避免创建不必要的对象

消除过期的对象引用

避免使用终结方法

参考资料

posted @ 2017-08-01 08:32  fireway  阅读(672)  评论(0编辑  收藏  举报