创建和销毁对象
考虑使用静态工厂方法代替构造器
类可以提供一个公有的静态工厂方法(public static factory method)来返回一个类的实例。
例如,Boolean类的valueOf()方法:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
提供public静态工厂方法而不是public的构造器的优势如下:
-
静态工厂方法有名称,不过要慎重地选择名称
例如,构造器BigIntger(int, int, Random)返回的BigInteger可能为素数,如果用名为BigInteger.probablePrime()的静态工厂方法显然更清晰。
示例: BigIntegerTest -
不必在每次调用静态工厂方法的时候都创建一个新对象
如果程序经常经常请求创建相同的对象,并且创建对象的代码很高,则这项技术可以极大地提升性能。
示例:BooleanTest
当且仅当a==b的时候才有a.equals(b)为true, 如果类保证了这一点, 它的客户端就可以使用==操作符来代替equals(Object)方法,这样可以提升性能。 -
静态工厂方法可以返回子类类型的对象
API返回的对象对应的子类可以不是公有的,这种方式可以隐藏具体的实现类。
这项技术适合于基于接口的框架,接口为静态工厂提供了自然返回类型。
接口不能有静态方法,因此按照惯例,返回接口的静态工厂方法是放在一个不可实例化(non-instantiability)的类中。
示例: InterfaceCannotStaticTest
示例: java.util.Collections是一个不可实例化的类,它提供了不可修改的集合、同步集合的静态工厂方法。我们模仿其写了一个简单例子: MyListTest
通过使用这种静态工厂方法,甚至要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象,这是一种良好的习惯。 为了提升软件的可维护性和性能,返回对象的(子)类也可能随着发行版本的不同而不同。
示例:EnumSetTest 发行版本1.5中引入java.util.EnumSet没有公有的构造器,只有静态工厂方法,返回两种实现类之一,具体则取决于底层枚举类型的大小。
示例:服务提供者框架 -
静态工厂方法在创建参数化类型实例的时候,使得代码更加简洁
例如,创建一个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并没有工厂方法,但是可以把这些方法放在你自己的工具类中。
公有的静态工厂方法的缺点:
- 类如果不含有公有的或者受保护的构造器,静态工厂方法返回的实例不能被子类化。
- 同样地,对于非公有类(nonpublic classes),静态工厂方法返回的实例也不能被子类化。 示例: MyListTest
- 静态工厂方法与其它的静态方法无法被明确区分,不像构造器那样在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. |
简而言之,静态工厂方法和公有构造器都各有用处。 静态工厂方法通常更加适合,因此切忌第一反应是提供公有的构造器,而不先考虑静态工厂方法。
遇到多个构造器参数时要考虑用构建器
静态工厂和构造器有一个共同的局限性:它们都不能很好地扩展到大量的可选参数。考虑用一个类表示一个食品的营养成分表。对于这样的类,应该用哪个构造器或者静态方法来编写呢?
- 重叠构造器(telescoping constructor)
示例: telescoping_constructor/NutritionFacts
① 当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。
② 如果读者想知道那些值是什么意思,必须很仔细地数着这些参数来探个究竟。如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会出错,但是在程序运行时会出现错误行为。 - JavaBeans模式:调用一个无参构造器来创建对象,然后调用setter()方法来设置每个必要的参数,以及每个相关的可选参数。
示例: javabeans/NutritionFacts
① 无法保证一致性:JavaBean模式自身有着很严重的缺点,因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处在不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败,这种失败调试起来十分困难。
② 阻止了把类做成不可变,这就需要程序员付出额外的努力来确保它的线程安全。 - 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 。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
通过私有构造器强化不可实例化的能力
避免创建不必要的对象
消除过期的对象引用
避免使用终结方法
参考资料
- 《Effective Java》第2版
- Java反射AccessibleObject类的setAccessible方法