Effective Java: 使用静态工厂方法

这是 Effective Java 的第一节的标题。本文更多的是摘译该节的内容。

什么是静态工厂方法(static factory methods)

static factory methods 翻译过来就是静态工厂方法。它并不是 GOF 提的设计模式中的一个设计模式。
我们看下面的例子(摘自JDK 1.7)。

public final class Boolean implements java.io.Serializable,
        Comparable<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);
    }
}

我们要获取一个 Boolean 的一个对象,可以使用构造函数 new Boolean(true) 也可以使用里面的静态方法 Boolean.valueOf(true),后者便是静态工厂方法。

静态工厂方法的优点

和直接使用构造函数相比,静态工厂方法的优点有:

  1. 静态工厂方法在方法命名上更具有可读性
    在使用构造函数去构造对象的时候,我们传递不同的参数构造不同类型的对象。如果不看文档的话,我们很难记住传递什么参数能够构造什么样子的对象。用静态的工厂方法就不一样啦,我们可以不同的工厂方法不同名字,我们就可以很容易的记住什么方法名可以构造什么样的对象。关于这一点,在 JDK 中最有说服力的一个例子就是 java.util.concurrent.Executors,里面N多的静态工厂方法:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool等等
  2. 静态工厂方法不需要每次在被调用的时候都构造一个新的对象
    也就是说我们调用静态工厂方法返回的可能是缓存的一个对象,而不是一个新的对象。这可以减少创建新的对象,从来提高性能,前面提到的 Boolean 就是一个例子。这对于 immutable 的对象特别有用,读到这里,我翻看了一下 JDK 的源代码,才发现原来 JDK 中原始数据类型(Primitive Data Types)的包装类都是 immutable 的。也就是说只要创建 Boolean、Integer、Long 等的对象,它的值就是不能改变的。我们再看 Integer 提供的静态工厂函数:

    public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    上面的代码把出现概率高的 int 做了一个 cache,这样每次只要返回 cache 里的对象,而不用新建一个对象。

  3. 静态工厂方法还可以返回该类型的子类对象
    这个特性让静态工厂方法的可扩展性大大的优于构造函数。在 JDK 中最典型的应该是 java.util.EnumSet。EnumSet 本身是 absract,我们无法直接调用它的构造函数。不过我们可以调用它的静态方法 noneOf 来创建对象,RegularEnumSet/JumboEnumSet 都继承至 EnumSet,noneOf 根据参数返回合适的对象。

    public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
        implements Cloneable, java.io.Serializable {
    
        EnumSet(Class<E>elementType, Enum[] universe) {
        }
    
        public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
            if (universe.length <= 64)
                return new RegularEnumSet<>(elementType, universe);
            else
                return new JumboEnumSet<>(elementType, universe);
        }
    }

  4. 静态工厂方法还可以简化参数化类型的对象创建
    这个优点优点语法糖的味道,不过语法糖人人都喜欢啦。

    Map<String, List<String>> m =
        new HashMap<String, List<String>>();
    
    public static <K, V> HashMap<K, V> newInstance() {
        return new HashMap<K, V>();
    }
    Map<String, List<String>> m = HashMap.newInstance();

    第一行冗长的代码我们就可以简化成第三行的代码。比较典型的例子就是 guava 中的 Maps (Google collections library)

静态工厂方法的缺点

静态工厂方法当然也有其缺点。

  1. 如果我们在一个类中将构造函数设为private,只提供静态工厂方法来创建对象,那么我们就不能通过继承的方式来扩展该类。不过还好的是,在需要进行扩展的时候,我们现在一般提倡用组合而不是继承。
  2. 第二个缺点是,静态构造方法不能和其他的静态方法很方便的区分开来。好吧,原文的意思是静态构造方法做的是一等公民(构造函数)的事,却得不到一等公民的待遇。
    为了和普通函数有所区分,原文建议在命名静态工厂方法的时候遵循一定的规则

    • valueOf — 返回和参数一样的对象,通常都用作类型转换,比如 Intger.valueOf(int i)
    • of — 和 valueOf 类似。
    • getInstance — 根据参数返回对应的对象,该对象可能是缓存在对象池中的对象。对于单例 singleton,我们使用无参数的 getInstance,并且总是返回同一个对象
    • newInstance — 和 getInstance 一样,不过这个方法的调用每次返回的都是新的对象。
    • getType — 和 getInstance 类似,不过区别是这个方法返回的对象是另外一个不同的类。
    • newType — 和 getType 类似,不过每次返回的都是一个新的对象。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注