01.考虑使用静态工厂方法替代构造方法
前言
《Effective Java》中文第三版,是一本关于Java基础的书,这本书不止一次有人推荐我看。其中包括我很喜欢的博客园博主五月的仓颉,他曾在自己的博文《给Java程序猿们推荐一些值得一看的好书》中也推荐过。加深自己的记忆,同时向优秀的人看齐,决定在看完每一章之后,都写一篇随笔。如果有写的不对的地方、表述的不清楚的地方、或者其他建议,希望您能够留言指正,谢谢。
《Effective Java》中文第三版在线阅读链接:https://github.com/sjsdfg/effective-java-3rd-chinese/tree/master/docs/notes
什么是构造方法?
定义:
- 一个在创建对象时自动被调用的方法。
特点:
- 构造方法的名称和类同名。
- 没有返回值类型,即不能使用 return 关键字。
- 普通方法不能以任何形式调用构造方法(构造方法中可以调用普通方法)。
注意:
- 当类中没有定义构造方法时,系统会默认添加一个无参的构造方法,当在类中定义构造方法的时候,默认的构造方法会消失。
实例:
public class Cat { //名字 private String name; //颜色 private String color; //年龄 private Integer age; //构造方法的名称和类同名 public Cat(){ } //带一个参数的构造方法 public Cat(String name){ this.name = name; //普通方法不能以任何形式调用构造方法,构造方法中可以调用普通方法 commonMethod(); } //有两个参数的构造方法 public Cat(String name, String color){ this.name = name; this.color = color; } public void commonMethod(){ } }
什么是静态工厂方法?
定义:
- 在类中提供一个公共的静态方法,通过这个静态方法,对外提供自身实例。
实例:
public class Cat { //名字 private String name; //颜色 private String color; //年龄 private Integer age; //创建只有名字的猫,带有一个构造参数 public static Cat createCatWithName(String name){ Cat cat = new Cat(); cat.name = name; return cat; } //创建只有颜色的猫,带有一个构造参数 public static Cat createCatWithColor(String color){ Cat cat = new Cat(); cat.color = color; return cat; } //创建有名字、颜色、年龄的猫 public static Cat createCatWithAllParam(String name, String color, Integer age){ Cat cat = new Cat(); cat.age = age; cat.name = name; cat.color = color; return cat; } }
为什么考虑使用静态工厂方法替代构造方法?(静态工厂方法的优点)
- 与构造方法不同,静态工厂方法可以有多个入参数量相同,但名称不同的方法。
实例:请参考上方的两个实例
- 与构造方法不同,静态工厂方法都是有名字的。生成的代码更易于阅读,也能突出它们的差异。
实例:
public class Test02 { private String s1; private String s2; private String s3; private Integer i1; public Test02(){ } public Test02(String s1){ this.s1 = s1; } public Test02(String s1, String s2){ this.s1 = s1; this.s2 = s2; } public Test02(String s1, Integer i1){ this.s1 = s1; this.i1 = i1; } public Test02(String s1, String s2, String s3){ this.s1 = s1; this.s2 = s2; this.s3 = s3; } public static Test02 createTest02InitAParam(String s1){ Test02 result = new Test02(); result.s1 = s1; return result; } public static Test02 createTest02InitTwoParam(String s1, String s2){ Test02 result = new Test02(); result.s1 = s1; result.s2 = s2; return result; } public static Test02 createTest02InitThreeParam(String s1, String s2, String s3){ Test02 result = new Test02(); result.s1 = s1; result.s2 = s2; result.s3 = s3; return result; } public static void main(String[] args) { //构造方法创建对象 //前面我们说过,构造方法的名称和类同名,这导致构造函数的名称不够灵活,不能准确的描述返回值。 //如果参数类型、数目比较相似的话,更加不容易找到合适的构造函数。例如test02与test04。 Test02 test0 = new Test02(); Test02 test01 = new Test02("A"); Test02 test02 = new Test02("A","B"); Test02 test03 = new Test02("A","B","C"); Test02 test04 = new Test02("A", 1); //静态工厂方法创建对象 //这让程序员更好的记得调用哪个方法创建适合自己需要的实例。注意仔细选择名称来突出它们的差异,这里我只是随便取的名字。 Test02 aParam = Test02.createTest02InitAParam("A"); Test02 twoParam = Test02.createTest02InitTwoParam("A", "B"); Test02 threeParam = Test02.createTest02InitThreeParam("A", "B", "C"); } }
- 与构造方法不同,静态工厂方法不用每次调用时都创建一个新对象。使用预先构建的实例,反复分配它们避免了不必要的重复。这个特点在创建新对象的代价非常昂贵的情况下,是可以极大地提高性能的。
实例:
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); }
- 与构造方法不同,静态工厂方法可以返回原返回类型的子类。例如一个应用是API,这让它可以返回对象而不需要公开它的类,这种方式隐藏类会使得API非常紧凑。
实例:
Class Person { public static Person getInstance(){ return new Person(); // return new Son01() / Son02() } } Class Son01 extends Person{ } Class Son02 extends Person{ }
- 与构造方法不同,静态工厂方法可以入参值的范围。作为类的提供者,类入参的范围越大,调用者就越容易出错。下方实例中,调用者只用传入type即可调用。当提供者期望传入构造函数的值是事先定好的值时(下方TYPE_A、TYPE_B),但如果不是,就很容易导致程序错误。避免这种错误,我们可以使用静态工厂模式。调用方无须知道也无需制定type值,这样就能控制type赋值的范围。
实例:
public class Test03 { public static final int TYPE_A = 1; public static final int TYPE_B = 2; public static final int TYPE_C = 3; int type; private Test03(int type) { this.type = type; } public static Test03 newA() { return new Test03(TYPE_A); } public static Test03 newB() { return new Test03(TYPE_B); } public static Test03 newC() { return new Test03(TYPE_C); } public static void main(String[] args) { Test03 test03 = newA(); } }
静态工厂方法的缺点
- 类如果不含有public或protected的构造器,就不能被子类化。原因是父类缺少公有的构造方法,而子类无法调用父类的私有构造方法,导致子类无法生成构造方法。
实例:
public class Person { private String name; private Integer age; private Person(String name, Integer age){ this.name = name; this.age = age; } public static Person getSon01(String name, Integer age){ return new Person(name, age); } public static Person getSon02(String name, Integer age){ return new Person(name, age); } //此时Student无法创建,因为Person没有公共的构造方法 public class Son extends Person{ } }
总结
静态工厂方法,语法层面:
- 能够有自己的名字
- 用子类使代码更加紧凑。
性能的层面:
- 避免创建大量等价的实例对象。
最重要的是,当我们作为类的提供者,能够更好的控制调用者的具体行为,减少调用方出错的行为。我认为这也是对自己代码负责的体现。