参考于《Effective Java》
前言
对于类而言,获得其实例最常见的方式就是提供一个构造器。
如果我们不写构造器,编译器会帮我们自动加上一个被public修饰的空构造器。
除了提供构造器以外,静态工厂方法也应该被考虑到程序的设计当中。
静态工厂方法
本质上就是类的一个静态方法,返回值是类的实例对象。
通过私有化构造器,无法直接new对象,而是通过运行静态工厂方法获取对象实例。
这么做既有优势,也有劣势。应该结合实际情况来使用。
优势
1、静态工厂方法有名称
我们知道,类的构造器名称必须和类名相同。
如果类比较复杂,需要多个构造器时,往往只能在构造器的方法签名上做文章。
通过不同的参数个数,参数类型,参数顺序来编写多个构造器不是个好主意。
面对这样的API,调用者往往不知所云,到底该调用哪一个构造器。
静态工厂方法可以很好的规避这一点,通过不同的方法名来区分,调用者就会一目了然。
例子
@Data
public class Person {
private static enum Sex{
MAN,WOMAN
}
private String name;
private Sex sex;
private Person(){}
public static Person createMan(){
Person person = new Person();
person.sex = Sex.MAN;
return person;
}
public static Person createWoman(){
Person person = new Person();
person.sex = Sex.MAN;
return person;
}
}
2、不必在每次调用时都创建新对象
使用构造器创建对象,每次都会返回一个新的对象。
静态工厂方法使得对象的创建是可控的。
可以将构建好的实例缓存起来进行重复利用,从而避免创建不必要的重复对象。
如果创建对象的代价很高,可以利用这项技术极大地提升性能。
Boolean.valueOf()很好的说明了这点。
它从不创建对象,返回的永远是自身的TRUE,FALSE常量。
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
还有Integer.valueOf()采用了缓存的机制。
如果数值在 IntegerCache.low 和 IntegerCache.high 之间将从缓存中返回。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
3、可以返回原返回类型的任意子类
构造器返回的只能是本类的对象实例。
但是静态工厂方法可以返回原返回类型的任意子类,这让我们选择返回类实例时大大的提高了灵活性。
当原先的类不能满足需求,需要替换类时,只需要修改静态工厂方法,而这一切对于调用者是零感知的。
public class Parent {
public void func(){
System.out.println("Parent Function.");
}
public static Parent getInstance(){
//返回任意子类实例
return new Child();
}
}
public class Child extends Parent {
@Override
public void func() {
super.func();
System.out.println("Child Enhanced Version.");
}
}
4、返回的对象的类可以随着每次调用而发生变化
比起构造器只能返回该类本身,静态工厂方法显得灵活的多。
其完全是我们可控的,只要是已声明的子类型都是允许的。
返回的类对象可能随着发行版本的不同而不同。
5、返回实例的类可以在编写方法时不存在
返回的子类可以在编写静态工厂方法时不存在,依赖于JDK提供的SPI机制,可以在需要时通过配置让JVM发现服务并加载。
可以先编写工厂方法,没有实现类也无所谓。
public interface SPIInterface {
void func();
}
public class SPI {
public static SPIInterface getInstance(){
ServiceLoader<SPIInterface> serviceLoader = ServiceLoader.load(SPIInterface.class);
Iterator<SPIInterface> iterator = serviceLoader.iterator();
if (iterator.hasNext()) {
return iterator.next();
}
throw new RuntimeException("没有可用的实现类");
}
public static void main(String[] args) {
SPIInterface instance = SPI.getInstance();
instance.func();
}
}
当需要提供服务时,按照SPI的规范来进行配置即可。
创建实现类
public class SPIImpl implements SPIInterface {
@Override
public void func() {
System.out.println("SPI 实现方法....");
}
}
SPI配置
在resources下新建文件:META-INF/services/unit1.item1.SPIInterface
,内容为:
unit1.item1.SPIImpl
重新运行上面的代码即可执行服务。
劣势
1、不能被子类化
构造器私有化后,该类将无法被子类化。
2、较难以发现
相对于传统的new方式创造对象,静态工厂方式较难以被发现。
而且在JavaDoc生成的文档中也没有像构造器一样被明确标识出来。
但是可以通过标准的命名习惯来弥补这一不足。
静态工厂方法命名规范
-
form
类型转换方法,它只有单个参数,返回该类型的一个相对应的实例。
Dated= Date.from(instant) ; -
of
聚合方法,带有多个参数,返回该类型的一个实例,把它们合并起来。
Set faceCards = EnumSet.of(JACK , QUEEN, KING]; -
valueOf
比 from 和 of 更烦琐的一种替代方法。
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE); -
instance/getInstance
返回的实例是通过方法的(如有)参数来描述的,但是不能说与参数具有同样的值。
StackWalke luke = StackWalke.getinstance(options); -
create/newInstance
像instance/getInstance一样,但create/newInstance能够确保每次调用都返回一个新的实例。
Object newArray = Array.newInstance(classObject,arrayLen); -
getType
像getInstance一样,但是在工厂方法处于不同的类中的时候使用。
FileStore fs = Files.getFileStore(path); -
newType
像newInstance一样,但是在工厂方法处于不同的类中的时候使用。
BufferedReader br = Files.newBufferedReader(path); -
type
getType和newType的精简版。
List litany = Collections.list(legacylLtany);