Effective Java 第二版笔记之考虑使用静态工厂方法代替构造器

字数1,386 大约花费5分钟

目录

  1. 1. 静态工厂方法可以根据用途自己定义名称
  2. 2. 静态工厂方法可能不用在每次调用时都创建新对象
  3. 3. 静态工厂方法可以返回声明类型的子类型的实例
  4. 4. 小结

创建对象最直接的想法就是通过 new 调用构造器,其实大多数情况下应当通过自己写一个静态公有方法,返回类的实例,比如下面这个方法:

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

和直接使用构造器相比,使用静态工厂方法具有以下优点:

  • 静态工厂方法可以根据用途自己定义名称(不必与类相同),可读性更强
  • 静态工厂方法可能不用在每次调用时都创建新对象
  • 静态工厂方法可以返回声明类型的子类型的实例

下面分别阐述这三个优点。

静态工厂方法可以根据用途自己定义名称

构造器的方法名只能使用类名,如果有多个构造器,只能通过参数类型甚至顺序来区分,这样可读性非常差,而且不容易记,调用的时候很容易出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TantanitReader {
private int age;
private String loginName;
private String realName;
private String career;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getLoginName() {
return loginName;
}

public void setLoginName(String loginName) {
this.loginName = loginName;
}

public String getRealName() {
return realName;
}

public void setRealName(String realName) {
this.realName = realName;
}

public String getCareer() {
return career;
}

public void setCareer(String career) {
this.career = career;
}
}

比如在上面的例子中,类 TantanitReader 表示我的官方博客 tantanit.com 的读者信息,其中 age 表示年龄,loginName 表示登录名,realName 表示真实姓名,career 表示职业,除了 age 是 int 类型之外,其它几个都是字符串类型。

假设现在需要根据年龄和登录名,创建一个读者,使用构造器的话,代码如下:

1
2
3
4
public TantanitReader(int age, String loginName) {
this.age = age;
this.loginName = loginName;
}

假设又有一个场景,需要根据年龄(int 类型)和真实姓名(String 类型)创建读者,如果使用构造器的话,参数类型和上一个构造器相同,要解决这个问题呢?机智如你,一定想到通过对调参数顺序这个取巧(而不优雅)的方法,来规避这个问题。

1
2
3
4
public TantanitReader(String realName, int age) {
this.realName = realName;
this.age = age;
}

如果一个类要通过构造器创建读者,就需要根据这两个构造器的参数顺序记住各自的用法,是不是很容易记错啊。就算记住了也要查文档或者看看 TantanitReader 这个类的源文件,再确认一遍吧。

好了,又来了一个场景,需要根据年龄(int 类型)和职业创建读者,如果使用构造器的话,相信聪明如你,也是没有办法了。

所以,应当使用静态工厂方法来代替构造器。下面的代码很好地解决了这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static TantanitReader getByAgeAndLoginName(int age,String loginName){
TantanitReader tantanitReader=new TantanitReader();
tantanitReader.setAge(age);
tantanitReader.setLoginName(loginName);
return tantanitReader;
}

public static TantanitReader getByAgeAndRealName(int age,String realName){
TantanitReader tantanitReader=new TantanitReader();
tantanitReader.setAge(age);
tantanitReader.setRealName(realName);
return tantanitReader;
}

public static TantanitReader getByAgeAndCareer(int age,String career){
TantanitReader tantanitReader=new TantanitReader();
tantanitReader.setAge(age);
tantanitReader.setCareer(career);
return tantanitReader;
}

三个方法使用不同的名称,并且在名称中暗示了参数的顺序,这样,在调用的时候,就不容易出错,也不用再查看文档或构造器的源码了。

静态工厂方法可能不用在每次调用时都创建新对象

使用静态工厂方法,在有些使用场景下,可以重复使用一个提前生成的对象,或者从缓存中获取一个对象,而不用创建一个新的对象。文章开头的例子中的 Boolean.valueOf(boolean)方法,调用时就不用创建新的对象。这样节省了内存开销,也提高了性能。同时,和每次都 new 一个新的对象,都是不同的对象相比,在这种重复使用的场景中,每次返回的对象都是严格意义相同的对象,可以做到对象级别的控制。这种控制对单例和不可实例化的使用场景很有用,并且可以放心的使用 == 代替 equals 方法,以提高性能。比如枚举类就使用了这样的技术。

静态工厂方法可以返回声明类型的子类型的实例

构造函数,只能返回该类的实例,不能返回该类的子类的实例,静态工厂方法不受这个限制,因此可以很好地使用 Java 语言的多态性。在方法声明返回值类型为父类型,甚至接口类型,而返回子类型或接口的某个类型的具体实现。这样,将来想返回其它子类型或接口的其它实现时,只要直接修改方法体,不用改方法的声明,从而不会影响调用方的使用。

小结

本文使用谈谈 IT 读者的例子介绍了使用静态工厂方法替代构造器的三个优点,在有些场景,这些优点会很明显,下次遇到了,记得考虑使用静态工厂方法哦!

谈谈 IT的文章均为原创或翻译(翻译会注明外文来源),转载请以链接形式标明本文地址: http://tantanit.com/effectiv-java-static-method-replace-constructor/

谈谈IT

欢迎关注官方微信公众号获取最新原创文章