- 《Effactive Java》学习笔记
- 创建和销毁对象
- Item 1: 考虑使用静态工厂方法来代替构造器
- 优点1:与构造器相比,静态工厂方法有他自己的名字
- 优点2: 工厂方法不要求每次都创建新对象
- 优点3: 静态工厂方法可以返回当前类的任何子类的对象
- 优点4:静态工厂方法所返回的对象的类可以随着每次调用而发生变化,这取 决于静态工厂方法的参数值
本文是《Effactive Java》学习笔记,记录其中描述的设计规范,没有过多的概念描述,力求精简。有代码 创建和销毁对象 Item 1: 考虑使用静态工厂方法来代替构造器
示例:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
注意:这里的静态工厂方法与设计模式里的工厂方法模式完全不是一个东西
这种方式有利也有弊
优点1:与构造器相比,静态工厂方法有他自己的名字一个类的不同构造器的行为往往具有不同的逻辑,而不同构造器功能只能通过他们所传递的参数来区分(或者通过注释),而有些构造器之间通过参数往往也难以区分。如:
public BigInteger(int bitLength, int certainty, Random rnd)//创建一个可能是素数的随机数 public BigInteger(int numBits, Random rnd)//创建一个指定范围内的数字
以上这两个BigInteger的构造器参数相似,而功能却完全不同。这时就可以通过使用静态工厂方法来为这两个构造器起个名字,如:
public static BigInteger probablePrime(int bitLength, Random rnd);//功能如其方法名称
这样就能很好表示要创建一个很可能为素数的BigInteger对象了。
一个类的构造器不能有相同的参数列表(类型、数量和顺序一致),这时如果两个不同功能的构造器恰好需要相同的参数列表。那么就只能修改参数顺序来区分不同的构造器,这是一种极其糟糕的方式。如下:
//错误示例 public User(Integer age, String name)//构造器,指定年龄和姓名 public User(Integer height, String name)//构造器,指定身高(单位毫米)和姓名
以上代码是编译不通过的,只能写成以下形式:
public User(Integer age, String name)//构造器,指定年龄和姓名 public User(String name,Integer height)//构造器,指定身高(单位毫米)和姓名
这种构造器重载的方式极其糟糕,因为API使用者无法很清晰地分辨他俩的区别,这样很容易导致构造器调用错误。此时可以使用静态工厂方法的方式,如下:
public static User ageAndName(Integer age, String name)//指定年龄和姓名 public static User nameAndHeight(String name,Integer height)//指定身高(单位毫米)和姓名优点2: 工厂方法不要求每次都创建新对象
每次调用构造方法都会创建一个新的对象,而静态工厂方法可以每次都返回相同的对象。这可以避免重复创建不必要的对象,特别是创建对象成本很高时,它能极大提高性能。这种特性被称作实例可控的(instance-controlled)。
如:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
因为布尔值只有两个,可以预先创建好这两个不可变实例,每次调用这个方法都不会返回新的实例,而是返回预先创建好的不可变实例。
持续更新中。。。
优点3: 静态工厂方法可以返回当前类的任何子类的对象这个优势使得静态工厂方法返回的对象的真实类型更具有灵活性。
这样的灵活性体现在我们可以返回当前类的子类对象而不必把子类设置为公有的。调用者只需要关心如何调用在父类(或接口)中声明的方法即可,而不必关心子类是如何实现的(事实上子类可能对调用者不可见)。这会使得对外暴漏的API很简洁。
例子:
如果我们不愿意暴漏某个接口的一些实现类(为了精简API或者其他想法),那么就可以定义一些静态工厂方法来返回这些实现类的对象。
在Java8之前,接口是不能有静态方法的,自然就不能在接口中定义静态工厂方法。这就出现了对应与这个接口的不可实例化的伴生类。如一个接口名称为MyInterface,那么它的伴生类通常命名为MyInterfaces。
如集合接口Collection的伴生类Collections。Collections中提供了相当多的(45个)Collection接口的非公有成员内部类,如不可修改的集合、 同步集合,等等。所有这些实现类都可使用Collections的静态工厂方法返回其实例对象。
public staticList synchronizedList(List list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); }
试想以下,如果把这45个接口实现类都定义为共有的,那么API使用者将大幅增加使用成本。提供命名友好、调用方便的静态工厂方法比直接调用指定子类的构造器要友好地多。
优点4:静态工厂方法所返回的对象的类可以随着每次调用而发生变化,这取 决于静态工厂方法的参数值Java8开始允许在接口中定义静态方法,那么我就就可以把存在于伴生类中的一些静态工厂方法移到接口中(这些静态工厂方法中的一些复杂逻辑建议写到私有包中的私有类中,不然方法太长了,因为接口中不支持定义私有静态成员和内部类)。
只要是静态工厂方法返回值类型地子类型都是允许返回的。这样方法返回值地类型可以随着版本地迭代而发生改变,且对调用者无感知。
例如:
EnumSet抽象类(为指定枚举类型的所有实例的集合提供高性能的集合相关操作)没有提供公有构造方法,它有两个实现子类JumboEnumSet和RegularEnumSet。EnumSet的静态工厂方法根据方法类型返回不同的子类实例。如:
public static> EnumSet allOf(Class elementType) { EnumSet result = noneOf(elementType); result.addAll(); return result; } public static > EnumSet noneOf(Class elementType) { Enum>[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); //根据枚举所有实例个数决定返回哪个 //64是因为RegularEnumSet内部用一个long类型来表示所有枚举值,一个比特对应一个枚举实例,而long类型长64比特 if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); }
如果以后有比RegularEnumSet更加高效的实现,则完全可以删除RegularEnumSet并增加新实现,且对于调用者是无感知的。



