《Effactive Java》学习笔记
文章目录
本文是《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
的静态工厂方法返回其实例对象。
/**
返回指定列表的西安城同步列表,其中SynchronizedRandomAccessList和SynchronizedList都是Collections中的非公有成员内部类
*/
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
试想以下,如果把这45个接口实现类都定义为共有的,那么API使用者将大幅增加使用成本。提供命名友好、调用方便的静态工厂方法比直接调用指定子类的构造器要友好地多。
Java8开始允许在接口中定义静态方法,那么我就就可以把存在于伴生类中的一些静态工厂方法移到接口中(这些静态工厂方法中的一些复杂逻辑建议写到私有包中的私有类中,不然方法太长了,因为接口中不支持定义私有静态成员和内部类)。
优点4:静态工厂方法所返回的对象的类可以随着每次调用而发生变化,这取 决于静态工厂方法的参数值
只要是静态工厂方法返回值类型地子类型都是允许返回的。这样方法返回值地类型可以随着版本地迭代而发生改变,且对调用者无感知。
例如:
EnumSet
抽象类(为指定枚举类型的所有实例的集合提供高性能的集合相关操作)没有提供公有构造方法,它有两个实现子类JumboEnumSet
和RegularEnumSet
。EnumSet
的静态工厂方法根据方法类型返回不同的子类实例。如:
/**
创建一个包含目标枚举类型的所有实例的EnumSet集合
*/
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
EnumSet<E> result = noneOf(elementType);
result.addAll();
return result;
}
/**
创建一个EnumSet集合空集合,枚举类型为指定类型
*/
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> 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
并增加新实现,且对于调用者是无感知的。
优点5:静态工厂方法返回的对象类型,在你写这个静态工厂方法时可以不存在其实现类
意思就是说,这个静态工厂方法可以返回以后可能提供的返回值类型的实现类对象,而你在写这个工厂方法时,可以没有这些实现类对象,但是方法内部存在机制来正确实例化并返回将来可能提供的方法返回值类型的实现类对象。
比如说JDBC
的Connection
接口表示一个与某数据库建立的链接,在制定这个接口时,它的各种实现类(比如到Mysql的连接)还不存在。但是能返回Connection
接口类型对象的静态工厂方法DriverManager.getConnection
内部的机制就能在你注册Connection
的实现类后(JDBC中就是注册数据库驱动)把该实现类的对象实例化并返回。
这样灵活的静态工厂方法是服务提供者框架的基础。
服务提供者框架一般包括三个组件:服务接口
(即这个框架可以提供的服务的抽象)、提供者注册API
(向服务提供者框架注册服务接口的实现)和服务访问API
(客户端用来获取服务接口实现实例的API)。如果服务接口存在多个实现,服务访问API可能会接收客户端的参数来获取指定实现类对象;或者返回默认实现;或者允许客户端轮询所有实现来获取他想要的那个。
服务访问API
就是一种灵活的静态工厂,它是服务提供者框架的基石。
服务提供者一个可选的第四个组件是:服务提供者接口
(注意它与服务接口
的区别,服务接口
所提供的服务的抽象类,而服务提供者接口是用来提供这些服务接口实现类实例的一组API)。它是用来生产服务接口
实例的对象工厂。如果没有服务提供者接口,那么框架就只能使用反射来实例化服务接口
实现类的对象了(注意是先有服务提供者框架,后有服务提供者实现类,服务提供者事先不知道服务接口的实现类,所以只能在运行时使用反射创建实现类的对象)。比如JDBC服务提供者框架,它的Connection
接口是服务接口,DriverManager.registerDriver
是服务注册API,DriverManager.getConnection
是服务访问API,Driver
是服务提供者接口
缺点1:只提供了静态工厂方法而没有提供public或protected的构造方法时,这个类不能被子类化
比如你无法继承任何Collections
类内部的任何便利的类。但也因此鼓励了程序员使用复合( omposition ),而不是继承来二次开发这些类,这正是不可变类型所需要的。
缺点2:程序员通常很难发现合适的被提供的静态构造方法
API 文档中,它没有像构造器那样在 API 文档中明确标识出来, 对于提供了静态工厂方法而不是构造器的类来说,要想查明如何例化类是非常困难的。因此可以在类或接口级注释中涉及静态工厂方法的描述。最好使用一些习惯性命名:
from
类型转换方法,它只有单个参数,返回该类型的一个相对应的实例,例如:Date = Date.from(instant);
of
聚合方法,带有多个参数,返回该类型的一个实例,把它们合并起来,例如Set<Rank> faceCard = EnumSet.of (JACK , QUEEN, KING)
;valueOf
一一比from
和of
更详细的替代方法,例如Bigintege prime = Biginteger.valueOf(Integer.MAX_VALUE);
instance
或者getInstance
-返回的实例是通过方法的参数(如有)来描述 的,但是不能说与参数具有同样的值,例如StackWalker luke = StackWalker.getinstance(options)
create
或者newInstance
一一像instance
或者getInstaηce
样,但create
或者newInstance
能够确保每次调用都返回一 新的实例 ,例如:Object newArray = Array.newInstance(classObject, arraylen);
get
+类型名,和getInstance
一 样,但是方法名称中指定了要返回的数据类型,例如:FileStore fs = Files.getFileStore(path)
new
+类型名,和newInstance
一样,,但是方法名称中指定了要返回的数据类型,例如:BufferedReader br= Files.newBufferedReader(path);
类型名
,前两种的精简版