这是 Effective Java 的第一节的标题。本文更多的是摘译该节的内容。
什么是静态工厂方法(static factory methods)
static factory methods 翻译过来就是静态工厂方法。它并不是 GOF 提的设计模式中的一个设计模式。
我们看下面的例子(摘自JDK 1.7)。
publicfinalclassBooleanimplements java.io.Serializable, Comparable<Boolean>{ publicstaticfinalBoolean TRUE =newBoolean(true); publicstaticfinalBoolean FALSE =newBoolean(false); publicstaticBoolean valueOf(boolean b){ return(b ? TRUE : FALSE); } }
我们要获取一个 Boolean 的一个对象,可以使用构造函数 new Boolean(true)
也可以使用里面的静态方法Boolean.valueOf(true)
,后者便是静态工厂方法。
静态工厂方法的优点
和直接使用构造函数相比,静态工厂方法的优点有:
-
静态工厂方法在方法命名上更具有可读性在使用构造函数去构造对象的时候,我们传递不同的参数构造不同类型的对象。如果不看文档的话,我们很难记住传递什么参数能够构造什么样子的对象。用静态的工厂方法就不一样啦,我们可以不同的工厂方法不同名字,我们就可以很容易的记住什么方法名可以构造什么样的对象。关于这一点,在 JDK 中最有说服力的一个例子就是
java.util.concurrent.Executors
,里面N多的静态工厂方法:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool等等 -
静态工厂方法不需要每次在被调用的时候都构造一个新的对象也就是说我们调用静态工厂方法返回的可能是缓存的一个对象,而不是一个新的对象。这可以减少创建新的对象,从来提高性能,前面提到的 Boolean 就是一个例子。这对于 immutable 的对象特别有用,读到这里,我翻看了一下 JDK 的源代码,才发现原来 JDK 中原始数据类型(Primitive Data Types)的包装类都是 immutable 的。也就是说只要创建 Boolean、Integer、Long 等的对象,它的值就是不能改变的。我们再看 Integer 提供的静态工厂函数:
publicstaticInteger valueOf(int i){ assertIntegerCache.high >=127; if(i >=IntegerCache.low && i <=IntegerCache.high) returnIntegerCache.cache[i +(-IntegerCache.low)]; returnnewInteger(i); }
上面的代码把出现概率高的 int 做了一个 cache,这样每次只要返回 cache 里的对象,而不用新建一个对象。
-
静态工厂方法还可以返回该类型的子类对象这个特性让静态工厂方法的可扩展性大大的优于构造函数。在 JDK 中最典型的应该是 java.util.EnumSet。EnumSet 本身是 absract,我们无法直接调用它的构造函数。不过我们可以调用它的静态方法 noneOf 来创建对象,RegularEnumSet/JumboEnumSet 都继承至 EnumSet,noneOf 根据参数返回合适的对象。
publicabstractclassEnumSet<E extendsEnum<E>>extendsAbstractSet<E> implementsCloneable, java.io.Serializable{ EnumSet(Class<E>elementType,Enum[] universe){ } publicstatic<E extendsEnum<E>>EnumSet<E> noneOf(Class<E> elementType){ if(universe.length <=64) returnnewRegularEnumSet<>(elementType, universe); else returnnewJumboEnumSet<>(elementType, universe); } }
-
静态工厂方法还可以简化参数化类型的对象创建这个优点优点语法糖的味道,不过语法糖人人都喜欢啦。
Map<String,List<String>> m = newHashMap<String,List<String>>(); publicstatic<K, V>HashMap<K, V> newInstance(){ returnnewHashMap<K, V>(); } Map<String,List<String>> m =HashMap.newInstance();
第一行冗长的代码我们就可以简化成第三行的代码。比较典型的例子就是 guava 中的 Maps (Google collections library)
静态工厂方法的缺点
静态工厂方法当然也有其缺点。
- 如果我们在一个类中将构造函数设为private,只提供静态工厂方法来创建对象,那么我们就不能通过继承的方式来扩展该类。不过还好的是,在需要进行扩展的时候,我们现在一般提倡用组合而不是继承。
- 第二个缺点是,静态构造方法不能和其他的静态方法很方便的区分开来。好吧,原文的意思是静态构造方法做的是一等公民(构造函数)的事,却得不到一等公民的待遇。
为了和普通函数有所区分,原文建议在命名静态工厂方法的时候遵循一定的规则
- valueOf — 返回和参数一样的对象,通常都用作类型转换,比如 Intger.valueOf(int i)
- of — 和 valueOf 类似。
- getInstance — 根据参数返回对应的对象,该对象可能是缓存在对象池中的对象。对于单例 singleton,我们使用无参数的 getInstance,并且总是返回同一个对象
- newInstance — 和 getInstance 一样,不过这个方法的调用每次返回的都是新的对象。
- getType — 和 getInstance 类似,不过区别是这个方法返回的对象是另外一个不同的类。
- newType — 和 getType 类似,不过每次返回的都是一个新的对象。