Effective Java 第三版读后感第一条:使用静态工厂方法代替构造器

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快速入门学习

看到这儿你可能会疯掉,这么多构造器。你第一时间可能不知道该用那个构造器,因为这些构造器通通没有名字。如果没有开发者文档的帮助的,一般是很难读懂的。而这里使用静态工厂方法:它的一个好处是它不受上面构造器没有名字的限制,因为静态工厂方法有名称,而且多个静态工厂方法名是不同的。在这里上面的每一个构造器都可以对应一个静态工厂方法,这样我们需要那种类型的构造,就调用响应的静态方法即可,例如下面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. 静态工厂方法可以根据方法的参数值返回不同的对象类

这一点就很好理解,因为这个就会让我们自然而然的想到设计模式之工厂模式,因为工厂模式就是通过传入不同的参数值,来返回不同的类对。还是以上面创建的AnimalTigerHorse说明,我们将上面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文档的帮助,程序员一般很难查明如何实例化对象。而且这通常会和类中的其他普通静态方法混淆。两者一般不好区分开来。

三. 总结

简而言之,静态工厂方法和公有构造方法都各有用处,我们需要理解它们各自的长处。静态工厂通常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值