java程序员对创建对象再也熟悉不过,一般为了获取一个对象通常会使用new
关键字,通过构造器方法来创建对象的实例,除此之外还有一个静态工厂方法也应该考虑在内。
文章目录
一. 静态工厂方法优势
下面五点都是Joshua Bloch大神
总结的静态工厂优与构造器创建对象的好处,我在每一点下面加入的个人的理解。有些点可能有理解不到位的地方,如果你有更好的想法欢迎留言,感谢各位大佬的建议和批评。
1. 静态工厂方法可以减少创建对象的次数
直接通过构造器获取实例对象是个简单和有效操作方法。但是对于访问量较大的对象,这种通过构造器对象:即我们常说的new 一个对象的方式
创建对象是不合适的,因为每一次调用对象的方法都创建新的对象,将会大量占有Jvm的有限的堆内存空间,而我们往往只需要一次创建对象,就可以完成类的方法调用,这里举个小小的例子:JDBC连接数据库的驱动只初始化一次就够了,JDBC的经典连接如下:
//加载驱动程序
Class.forName(driver);
//1.getConnection()方法,连接MySQL数据库!!
Connection con = DriverManager.getConnection(url,user,password);
这儿将会加载com.mysql.Driver
驱动(假设使用的是mysql),我们可以看看底层Driver这个类做了什么?
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
mysql驱动的操作是在静态代码块中进行的,静态代码块只会在初始化加载一次。
外部调用者只需要拿到一个实例对象即可,通过静态工厂方法
能够为重复的对象返回相同的对象,这些以来就减少了不必要的对象的创建以节约内存开销,这通常又让我们想到单例设计模式和享元模式,而它们通常都是通过静态方法来实现的。
2. 静态工厂方法与构造器优势在于,静态工厂方法有名字
由于java语言的特性,当提供有参的构造器,根据不同成员变量组合多少可以创建多个不同的有参构造器方法,例如joda-time
中的DateTime
类。让我们来看看它的构造有那些。如果你想对joda-time
有更多的了解:不妨看一下这里:☞ joda-time快速入门学习
![](https://i-blog.csdnimg.cn/blog_migrate/3418203f99203a7f0afe65e91c2a827a.png)
看到这儿你可能会疯掉,这么多构造器。你第一时间可能不知道该用那个构造器,因为这些构造器通通没有名字。如果没有开发者文档的帮助的,一般是很难读懂的。而这里使用静态工厂方法
:它的一个好处是它不受上面构造器没有名字的限制,因为静态工厂方法有名称,而且多个静态工厂方法名是不同的。在这里上面的每一个构造器都可以对应一个静态工厂方法,这样我们需要那种类型的构造,就调用响应的静态方法即可,例如下面Student类,我们可以这么做:
/**
* 学生类对象
*/
@Data
public class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 年级
*/
private int schoolGrade;
/**
* 学校
*/
private String schoolName;
/**
* 班级
*/
private String schoolClass;
// 构造方式1
public Student(String name, int age) {
this.age = age;
this.name = name;
}
// 构造方式2
public Student(String name, int age, int schoolGrade, String schoolName, String schoolClass) {
this.name = name;
this.age = age;
this.schoolGrade = schoolGrade;
this.schoolName = schoolName;
this.schoolClass = schoolClass;
}
// 构造方式3
public Student(int schoolGrade, String schoolName, String schoolClass) {
this.schoolGrade = schoolGrade;
this.schoolName = schoolName;
this.schoolClass = schoolClass;
}
}
使用静态方法优化:
// 构造优化方式1
public static Student method1(String name, int age) {
Student student = new Student();
student.name = name;
student.age = age;
return student;
}
// 构造优化方式2
public static Student method2(String name, int age, int schoolGrade, String schoolName, String schoolClass) {
Student student = new Student();
student.name = name;
student.age = age;
student.schoolGrade = schoolGrade;
student.schoolName = schoolName;
student.schoolClass = schoolClass;
return student;
}
// 构造优化方式3
public static Student method2( int schoolGrade, String schoolName, String schoolClass) {
Student student = new Student();
student.schoolGrade = schoolGrade;
student.schoolName = schoolName;
student.schoolClass = schoolClass;
return student;
}
在创建对象的时候可以根据method1、method2、method3名称来创建一个Student
对象
// 只像创建只有名字和年龄的学生对象,上面就是调用method1
Student student = Student.method1("张三", 23);
3. 静态工厂方法可以返回类的子类型
显然类的构造器只能返回类自身类型,而静态工厂就比较灵活,它可以返回类的子类类型。这点符合面向对象设计的基本原则之一里氏替换原则
: 即子类对象可以替代父类(多态的一种体现)
例如 一个父类对象Animal中静态的getInstance方法名,返回的对象可以是Animal类型,也可以是Tiger和Horse类型。
public class Animal {
public static Animal getInstance() {
// 这里可以返回父类Animal,也可以返回Tiger或者Horse,所以比构造器创建对象要更加灵活一些。
return new Animal();
}
}
class Tiger extends Animal {
}
class Horse extends Animal {
}
4. 静态工厂方法可以根据方法的参数值返回不同的对象类
这一点就很好理解,因为这个就会让我们自然而然的想到设计模式之工厂模式,因为工厂模式就是通过传入不同的参数值,来返回不同的类对。还是以上面创建的Animal
、Tiger
、Horse
说明,我们将上面Animal类的getInstance
方法修改如下:
public class Animal {
// 吃荤的类型
private static final int TYPE_EAT_MEAT = 0;
// 吃素的类型
private static final int TYPE_EAT_GRASS = 1;
/**
* 根据传入参数的不同返回不同类型的
*/
public static Animal getInstance(int type) {
// 这里可以返回父类Animal,也可以返回Tiger或者Lion,所以比构造器创建对象要更加灵活一些。
if (TYPE_EAT_MEAT == type) {
return new Tiger();
}
if (TYPE_EAT_GRASS == type) {
return new Horse();
}
return new Animal();
}
}
5.静态工厂方法返回的对象类:在编写该静态方法时候可以不存在
静态工厂的第5个优势在原文是这么说的:
A fifth advantage of static factories is that the class of the returned object need not exist when the class containing the method is written.
刚刚开始读这句话,感觉还是比较绕口。仔细想想就明白了,静态工厂方法返回的类对象可以动态扩展。而在那之前返回的对象可以是不存在的。这儿还是以上面的Animal类举例,这儿我提供一个getAnimal的方法,至于有什么用,往下看咱们就能更好理解第五大优势,这儿在原来的类增加一个静态方法,就像下面这样:
// 根据不同的class字节码创建不同的对象,这儿为了方便并没有对传入的字节码类型做校验。
public static <T> T getAnimal(Class c) throws Exception {
T o = (T) c.newInstance();
if (Animal.class.isInstance(o)) {
return o;
}
return null;
}
新增fish
类:
@Setter
@Getter
public class Fish extends Animal {
private final String name="鲈鱼";
}
mian方法测试如下:下面使用了java8新特性之Optional类,如果你对这个类不熟悉的话,不妨看一下这里:☞ 快速入门 java8-Optional类学习
public static void main(String[] args) throws Exception {
Fish fish = Animal.getAnimal(Fish.class);
Optional<Fish> fish = Optional.ofNullable(fish);
if (!fish.isPresent()) {
// 输出语句1
System.out.println("对象为null");
} else {
// 输出语句2
System.out.println("对象不为null:" + fish.getName());
}
}
看到这儿,你可能明白第五条优势说的含义了。正如你想的那样,Animal类提供一个返回Animal类型的静态方法getAnimal
,这里可以提供任意Animal子类的对象返回实现。即这个静态方法返回值可以动态进行变化。即使这些对象之前不存在。所以当运行这个main方法的时候,将会执行输出语句2,并打印出对象不为null:鲈鱼
。
二. 静态工厂方法劣势
1. 提供静态方法的类必须提供公有或者受保护的构造器
一个提供静态工厂的方法的类,若父类构造器私有了,那么子类无法继承父类。我们都知道子类初始化之前会先完成父类的初始化,所以父类的构造器对必须子类可见(非private
)。
2. 静态工厂的方法很难被发现
在这里Joshua Bloch大神也说了如果没有API文档的帮助,程序员一般很难查明如何实例化对象。而且这通常会和类中的其他普通静态方法混淆。两者一般不好区分开来。
三. 总结
简而言之,静态工厂方法和公有构造方法都各有用处,我们需要理解它们各自的长处。静态工厂通常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。