此系列文章为本人对《Effective Java》一书的学习笔记,主要是记录对书中重点内容的理解。
既然有缘看到此文,那么希望能对你有所帮助。
本文对应原书第1条 用静态工厂方法代替构造器
获取类实例的方法:
- 类的构造器
- 静态工厂方法(注:其和设计模式中的工厂方法模式并不相同)
对于前者,我们相对熟悉,直接通过new
关键字使用类的构造方法即可获取类的实例。
而后者,则是提供一个public的静态工厂方法(static factory method
)以返回类的实例。
静态工厂方法的优势:
- 静态工厂方法具有名称
由于构造器的名称无法进行自定义,所以在多个构造器同时存在的情况下,容易出现混乱,只能通过调整参数顺序等方式加以区分,而这种方式,又容易让使用者迷惑,不知道到底应该使用哪一个构造器。
而静态工厂方法则可以规避这一问题,可以通过自定义名称轻松突出方法与方法之间的区别,从而便捷用户的理解与选择。
// 构造器
public User(Long id, String name) {
// 利用姓名实例化
...
}
public User(String nickName, Long id) {
// 利用昵称实例化
...
}
// 静态工厂方法
public static User getUserByName(Long id, String name) {
return new User(id, name);
}
public static User getUserByNickName(Long id, String nickName) {
return new User(nickname, id);
}
- 不必每次调用时创建一个新对象
这一条非常重要,众所周知new
关键字每次都会新生成一个对象,而静态工厂方法则可以为我们提供了单例的思路。
我们可以在第一次调用时检查该类是否已存在实例,若存在即返回该实例,不存在则进行初始化,这样的操作使得性能、效率获得极大的提升(也正是Spring默认使用单例的原因)。
public class Pet {
private static Pet INSTANCE;
// 私有构造方法 确保安全
private Pet() {
}
// 静态工厂方法
public static Pet getInstance() {
if (INSTANCE == null) {
// 处理线程安全性
synchronized (this) {
if (INSTANCE == null) {
INSTANCE = new Pet();
}
}
}
return INSTANCE;
}
}
- 可以返回原返回类型的任何子类型的对象
构造方法只能返回所属类的实例对象,而静态方法,不仅可以返回自身的实例对象,还可以返回任何一个子类对象。
这样我们在选择返回对象的类时,就有了更大的灵活性。
public class Pet{
...
public static Pet getPet() {
return new Cat();
}
}
public class Cat extends Pet {
...
}
- 所返回对象的类可以随着每次调用而发生变化
返回对象的类并不是固定的,完全可以利用参数返回不同的类实例。
public class Pet{
public static Pet getPet(String name) {
Pet pet;
switch (name) {
case "Cat":
pet = new Cat();
break;
case "Dog":
pet = new Dog();
break;
default:
pet = new Pet();
break;
}
return pet;
}
}
public class Cat extends Pet {
...
}
public class Dog extends Pet {
...
}
- 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
还是以上述的宠物为例,你的静态工厂方法是提供一个Pet实例,可能在在最初的时候,什么品种也没有,但后来新增了几个品种,此时你的方法就可以返回这些新的品种。
public class Pet{
// 存放品种
private static final Map<String, Pet> map = new HashMap<>();
// 根据名称获取宠物
public static Pet getPet(String name) {
return Optional.ofNullable(map.get(name)).orElseThrow(() -> new IllegalArgumentException("还没这个品种"));
}
// 后续增加品种,将其放入map
public static void add (String name, Pet pet) {
map.put(name, pet);
}
public static void main(String[] args) {
try {
getPet("Cat");
}catch (Exception e) {
// 首次获取时由于map里没有Cat,会报错
System.out.println(e.getMessage());
}
// 动态进行品种的添加
add("Cat", new Cat());
// 再次获取则可以得到猫的实例
System.out.println(getPet("Cat"));
}
}
静态工厂方法的劣势:
-
类如果不含public或protected构造器,就不能被子类实例化
在使用静态工厂方法时,由于经常会处于保护目的,将构造器私有化,由于子类实例化时需要调用父类的构造器,所以私有的构造器会导致类无法被继承。
处理的办法是,用复合(composition
)取代继承,以实现类的正常拓展。 -
难以被使用者(程序员)发现
由于静态工厂方法没有强行的约束,具有较高的自由,这样的自由也使得它不容易被使用者所发现,所以在没有固定规范出台之前,遵循一些惯用名称会是一个比较好的方法。常用的名称有:from
: 类型转换方法,它只有单个参数,返回该类型的一个相对应的实例;of
: 聚合方法,带有多个参数,返沪该类型的一个实例,把他们合并起来;valueOf
:from
和of
的一种替代方法;instance
/getInstance
: 返回的实例是通过方法的参数(如有)来描述;create
/newInstance
: 像instance
或getInstance
一样,但其能够确保每次都获得新的实例;getType
: 像getInstance
一样,但是在工厂方法处于不同的类中的时候使用;newType
: 像newInstance
一样,但是在工厂方法处于不同的类中的时候使用;type
:getType
和newType
的简化版;
总结
简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解他们各自的长处。
而静态工厂在实际项目中往往更加适合,因此你需要优先考虑到它。
水平有限,若文章中存在错误,恳请不吝赐教,这对我以及后面的读者都有重要意义;
若文章能够帮助到你,还望一键三连,你的支持,是我最大的动力。