用静态工厂方法替换构造函数

这是根据effective java中的Item1条目来写的。在写java程序时,我们构造一个实例用的最多的就是调用类的构造函数.如A a = new A().但是还存在一种方法可以获取类的实例。而且相对于调用类的构造函数有很多好处,那就类的静态工厂函数。比如,在Java中的Boolean.valueOf(boolean b)方法:

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

静态工厂函数相对于构造函数有优点也有缺点,下面会依次进行说明

优点

静态工厂函数相对于构造函数有具体的名字

首先,有具体的名字有什么好处呢?如果有名字,可以让调用者更加清楚此函数的作用。因为构造函数的名字要相同,那如果构造函数做的事情不一样,则只能通过增加或删除构造函数的入参才能表达不同的构造函数。这样子对于调用者会造成负担,调用者必须了解构造函数代码做的事情,才能知道此构造函数的真正目的。而使用静态工厂函数,可以通过合理取函数名称,来表达不同的目的以及区别。

静态工厂函数每次被调用时可以不需要创建新的对象

一般我们调用构造函数时,都是会返回一个新的对象。但是有时候我们不需要返回新的对象,比如单例模式。

静态工厂函数可以返回静态工厂函数返回类型的子类型

这个是什么意思呢?比如我们拿java中的Collections的静态工厂方法synchronizedMap为例:

1
2
3
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}

可以看到函数定义的返回类型是Map,但是实际返回的类型是SynchronizedMap.但是这样做有什么好处呢?
这种方式可以隐藏实现类,从而形成一个非常紧凑的API。我们还是以Collections类为例。Collections框架中有45个接口实现工具实现,提供了不可修改集合、同步集合等。这些都是通过Collections提供静态工厂函数来实现,而且返回的子类型都是非公共的。所以Collections框架API比它导出45个独立的公共类要小的多,这样不仅减少了API的数量,同时也减少了框架使用者需要了解的概念。框架使用者不需要了解这45个实现类。同时因为返回的是接口类型,这也是很好的做法。

第四个优点,静态工厂函数返回的类可以根据输入参数来改变

静态工厂方法返回的类可以根据版本而改变。Java中的EnumSet类没有公共构造函数,只有静态工厂方法。

1
2
3
4
5
6
7
8
9
10
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");

if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}

通过代码我们可以看出,noneOf返回两个子类中的一个实例,这取决于底层enum类型的大小。如果有64个或更少的元素,那么返回一个long类型的RegularEnumSet实例;如果enum类型有65或更多的元素,方法将返回一个long[]类型的JumboEnumSet实例。
客户端感知不到这两个实现类的存在。如果 RegularEnumSet 不再为小型 enum 类型提供性能优势,它可能会在未来的版本中被消除,而不会产生不良影响。类似地,如果事实证明 EnumSet 有益于性能,未来的版本可以添加第三或第四个 EnumSet 实现。客户端既不知道也不关心从工厂返回的对象的类;它们只关心它是 EnumSet 的某个子类。

第五个优点,编写包含静态工厂函数的类时,此静态工厂函数返回对象所属的类可以不存在

上面这句话,听起来有点难以理解。这里做个例子进行说明,比如我们正在编写类A,其中包含factory1 静态工厂函数,factory1函数的定义返回类型是接口 Interface1,而实际

这一点暂时没有理解

缺点

第一个缺点,类如果没有公有或者受保护的构造器,那么此类就不能被继承。

例如,我们不能继承Collection框架中的任何类

第二个缺点,我们不太容易在类文档中找到静态工厂函数

因为Javadoc 工具不能明确的标注静态工厂函数,所以在文档中静态工厂函数就不会突出显示。而构造函数在文档中会突出显示。我们也可以遵守一些给静态工厂函数起名的规范,下面是一些建议:

  1. from: 将输入转为与之对应的相关类,接受一个参数,然后返回对应的类实例
    1
    Date d = Date.from(instant);
  2. of:接受多个参数,返回包含多个参数的类实例
    1
    Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  3. valueOf: 介于from和of之间
    1
    BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  4. instance 或者getInstance:返回参数对应的类实例,但是值有可能会改变
    1
    StackWalker luke = StackWalker.getInstance(options);
  5. create 或者 newInstance: 类似于instance或getInstance,但是每次调用都会返回一个新的实例
    1
    Object newArray = Array.newIntance(classObject, arrayLen);
  6. getType: 类似getInstance,获取实例,但是静态工厂函数在另一个类中。Type表面返回实例的类型
    1
    FileStore fs = Files.getFileStore(path);
  7. newType: 类似newInstance,但是静态工厂函数在另一个类中
    1
    BufferedReader br = Files.newBufferedReader(path);
  8. type: 一种精确的转换
    1
    List<Complaint> litany = Collections.list(legacyLitany);

总结

构造器和静态工厂函数有各自的优点和缺点,但是一般来说,静态工厂函数要比构造器好。在平常的编程过程中,我们不要一上来就直接写构造函数,可以先考虑一下,用静态工厂函数是不是更好。