Java性能优化(1):用静态工厂方法代替构造函数
对于一个类,为了让客户获得它的一个实例,最通常的方法是提供一个公有的构造函数。实际上还有另外一种技术,尽管较少为人所知,但也应该成为每个程序员的工具箱中的一部分。
类可以提供一个公有的今静态工厂方法。所谓静态工厂方法,实际上只是一个简单的静态方法,它返回的是类的一个实例。下面是来自一个Boolean类的简单例子。其中静态工作方法是1.4版本新增的,它把一个Boolean原语值转换为一个Boolean对象引用:public static Boolean valueOf(boolean b) {
return (b ? Boolean.TRUE : Boolean.FALSE);
}
类可以为它的客户提供一些静态工厂方法,来代替构造函数,或者同时也提供一些构造函数。用静态工厂方法来代替公有的构造函数,既有好处,也有不足之处。
静态工厂方法的优点
静态工厂方法的一个好处是,与构造函数不同,静态工厂方法具有名字。如果一个构造函数的参数并没有确切的描述被返回的对象。例如,构造函数BigInteger(int,int,Random)返回的BigInteger可能是素数,然而,如果使用一个名为BigInteger.probablePrime的静态工厂方法,表达显然更为清楚。
一个类只能有一个原型相同的构造函数。程序员通常知道该如何绕开这种限制,他可以提供两个构造函数,它们的参数列表只是在参数类型的顺序上有所不同。这并不是一个好主意,面对这样的API,用户永远也记不住该用哪个构造函数,结果常常会调用到错误的构造函数上。并且,读到使用这样的构造函数的代码往往会不知所云,除非去查看该类的文档。
因为静态工厂方法自己有名字,所以它们没有构造函数那样的限制,对于给定的原型特征,可以有不止一个静态工厂方法。如果一个类看起来需要多个构造函数,并且它们的原型特征相同,那么你应该考虑使用静态工厂方法来代替其中一个或者多个构造函数,并且慎重选择它们的名字以便明显的标示出它们的不同。
静态工厂方法第二个好处是,与构造函数不同,它们每次被调用的时候,不要求非得创建一个新的对象。这使得一些非可变类可以使用一个预先构造好的实例,或者把已经构造好的实例缓存起来,以后再把这些实例分发给客户,从而避免不必要的重复对象。Boolean.valueOf(boolean)方法说明了这项技术:它从来不创建对象。如果一个程序要频繁地创建相同的对象,并且创建对象的代价很昂贵,则这项技术可以极大地提高性能。
静态工厂方法可以重复的调用返回同一个对象,这也可以被用来控制“在某一时刻哪些实例应该存在”。这样做有两个理由。第一,它使得一个类可以保证是一个singleteon。第二,它使非可变类可以保证”不会有两个相等的实例存在”,即当且仅当a==b的时候才有a.equals(b)为true。如果一个类保证了这一点,那么它的客户就可以使用==操作符来代替equals(Object)方法,其结果是实质性的性能提高。类型安全枚举模式实现了这样优化,而String.intern方法以一种有限的形式实现了这种优化。
静态工厂方法第三个好处是,与构造函数不同,它们可以返回一个原返回类型的子类型的对象。这样我们在选择被返回的类型时就有了更大的灵活性。
这种灵活性的一个应用是,一个API可以返回一个对象,同时又不使该对象的类成为公有的。以这种方式把具体的实现隐藏起来,可以得到一个非常简洁的API。这项技术非常适合于基于接口的框架结构,因为在这样的框架结构中,接口成为静态工厂方法的自然返回类型。
例如,Collection Framework API比导出20个独立的公有类的那种实现方式要小的多,这不仅仅是值API数量上的减少。用户知道,被返回的对象时由相关的接口精确指定的,所以他们不需要阅读有关的类文档。更进一步,使用这样的静态工厂方法,可以强迫客户通过接口来引用被返回的对象,而不是通过实现类来引用被返回的对象,这是一个很好的习惯。
公有的静态工厂方法所返回的对象的类不仅可以是非公有的,而且该类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。只要是已声明的返回类型的子类型,都是允许的,而且,为了增强软件的可维护性,返回对象的类可以随着不同的发行版本而不同。
静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以并不存在,这种灵活的静态工厂方法构成了服务提供框架的基础,比如Java密码系统扩展(JCE),服务提供者框架是指这样的一个系统:提供者为框架的用户提供了多个API实现,框架必须提供一种机制来注册这些实现,以便用户能够使用它们。框架的客户直接使用API,无需关心自己到底在使用哪个实现。
在JCE中,系统管理员通过编辑一个被人熟知的properties文件,加入一个条目,把单个字符串(string key)映射到对应的类名,以此来注册一个实现类,客户使用静态工厂方法的时候把字符串键作为参数传递进去。静态工厂方法在一个映射表中查找class对象,该映射表根据properties文件进行初始化,然后静态工厂方法使用Class.newInstance方法实例化这个类,下面的程序框架说明了这项技术:
public abstract class Foo { |
静态工厂方法的缺点
静态工厂方法的主要缺点是,类如果不含公有的或者受保护的构造函数,就不能被子类化。对于公有的静态工厂所返回的非公有类,也同样如此。例如,想要子类化Collections Framework中的任何一个方便的实现类,太难了。然而这会因祸得福额,它会鼓励程序猿使用复合结构,而不是继承。
静态工厂方法的第二个缺点是,它们与其它静态方法没有任何区别。在API文档中,它们不会像构造函数那样被明确标示出来。而且,静态工厂方法代表了一种对规范的背离,因此,对于提供了静态工厂方法而不是构造函数那样明确标示出来。而且,静态工厂方法代表了一种对规范的背离,因此,对于提供了静态工厂方法而不是构造函数的类来说,要想在类文档中说明如何实例化一个类,这是非常困难的。如果遵守标准命名习惯,就可以将这个缺点减小到最少。这些命名习惯仍在演化中,但是静态工厂方法的两个名字已经变得很流行了:
valueOf——不太严格地讲,该方法返回的实例与它的参数具有同样的值。使用这个名字的静态工厂方法是一些非常有效的类型转换操作符。
getInstance——返回的实例是由方法的参数来描述的,但是不能够说与参数有同样的值。对于singleton的情形,该方法返回唯一的实例,这个名字在提供者框架中用得很普遍。
总得来说,静态工厂方法和公有的构造函数都有它们各自的用途,我们需要理解它们各自的长处。要避免一上来就提供构造函数,而不是考虑静态工厂,因为静态工厂通常更加适合。如果你正在权衡这两种选择,有没有其他因素强烈地影响你的选择,那么还是老老实实地用构造函数吧,毕竟人家是规范。