1. 静态工厂方法代替构造器
简介
获取类的实例,除了提供公有的构造器外,还可以使用静态工厂方法
静态工厂方法 提供实例,不同于设计模式中的 工厂方法模式,简单示例:
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
优势:
相对于构造器,静态工厂方法有如下优势
- 他们有名称.如:
//不需要用参数列表来表达语义,针对需要多个相同签名的构造器(调整构造器参数顺序也可达到目的)
public boolean isProbablePrime(int certainty) {
if (certainty <= 0) {
return true;
}
return getBigInt().isPrime(certainty);
}
- 不是每次调用时都创建一个新对象.
针对创建对象代价很高的场景,类似于
Flyweight模式
.不可变类可以预先构建好实例
,或者将构建好的实例缓存
起来.
- 可以返回原返回类型的任何子类型对象.
Collections类,返回隐藏的实现类,后续可更改实现,示例代码:Services.java
- 创建更加简洁的参数化类型实例,如:
Map<String, List<String>> m = new HashMap<String, List<String>>();
可以精简为如下方式,虽然在java8中已经好太多.
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
缺点:
- 如果不包含
public/protect
的构造器,就不能被子类化 - 与其他静态方法实际上没有任何区别.因此需要用
命名规范
来弥补,如:
valueOf()
of()
getInstance
newInstance
getType
newType
2.多个参数考虑用builder
问题
无论是
静态工厂
还是构造器
,对大量可选参数的扩展都不好.一向的办法:
- 使用
重叠构造器
模式,缺点是
会给调用者带来疑惑.必须仔细小心,防止参数传递错误. - 使用
JavaBeans
模式,缺点是
设置参数被分到几个调用中,会导致JavaBean
状态不一致,
也就是说JavaBeans
阻止了把类做成不可变的可能
Builder
:
优点:
既保持了重叠构造器那样的安全性,也保证了
JavaBeans
模式那样好的阅读性.
Builder
模拟了具名的可选参数.- 可以对其参数强加约束条件,在对象域中对参数进行检查.
- 可以有多个可变参数
- 可以用单个
Builder
创建多个对象
通常情况下,
Builder
常与有限制的通配符(如Builder<? extends Node>
)一起使用
public interface Builder<T>{
public T build();
}
拓展
Java
中 传统的抽象工厂实现 Class
存在一些问题,newInstance
充当了build
的一部分,总是试图调用无参的构造函数(无论存不存在),
无参构造不存在也不会编译时错误
,因此破坏了编译时的异常检查
.而通过Builder
就不会有这个问题.
缺点:
- 必须先创建
Builder
的构建器,实践中的开销并不十分明显 - 比重叠构造器模式更加的
冗长
,适合参数
比较多的情况(且后期可能增加参数
).
小结
- 针对构造器或静态工厂中有多个参数,且参数不固定的时候,
Builder
模式是不错的选择. - 否则,用静态工厂,或者构造器会是更好的选择.
3.使用私有构造器或者枚举强化Singleton
1.5之前的两种方法
私有构造器,公有静态成员
私有构造器,公有静态方法
缺点: 这两种方式,可以通过反射机制(
setAccessible
)来进行破坏,可以在构造器创建第二个实例的时候抛出异常来避免.优点: 工厂方法很灵活,可以不改变
API
,而改变类是否是Singleton
提醒: 为了防止反序列化创建新实例,需要声明
实例域是transit的
,并且提供readResolve
方法,实例如下:
private static transient final Elvis INSTANCE = new Elvis();
private Object readResolve(){
return INSTANCE;
}
枚举实现方式
示例: Elvis.java
Java 1.5 后可以通过单个元素的枚举实现单例,已经成为实现Singleton
的最佳方式,有如下好处:
绝对防止多次实例化
防止反序列化共计
单元素的枚举类型 已经称为实现
Singleton
最佳方法.
4.通过私有构造器强化不可实例化的能力
Utility
不需要实例化,如果没有显示构造器,但是编译器会默认提供一个 公有/无参 的构造器.
通过抽象防止类被实例化是行不通的
- 这样该类就可以子类化,并且子类可实例化.
- 会误导用户,以为是
专为继承
而设计的
明智的做法是:
- 私有默认构造器,保证外部不可以访问
- 默认构造器抛出异常,避免类内部不小心调用.
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
}
5.避免创建不必要的对象
概述
尽量重用对象,如果一个对象是不可变
的,就可以始终被重用.
示例: Person.java
反面例子:
//每次都会创建一个新的"string"
String s = new String("string");
应该使用
String s = "string";
对于
同时
提供了静态工厂方法
和构造器
的类,通常都是直接使用
静态工厂方法
.
案例:
Map#keyset
方法,对于一个给定的Map
对象,每次调用都返回同一个Set
实例.优先使用基本类型,而不是装箱类型
public static void main(String[] args) {
Long sum = 0L;//这里每次都会构造一个实例,多创建了2^31个实例.
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
小结
- 这里并不是说创建对象是十分昂贵的,如果能提高程序的清晰性,简洁性,多创建通常是好事.
- 除非
Object pool
中的对象是重量级的,否则无需维护自己的对象池.
6.消除过期对象引用
示例:
//class Stack
public Object pop(){
if(size ==0){
throw new EmptyStackException();
}
Object result= elements[size--];
elements[size] = null;//清空过期引用
return result;
}
清空过期引用的好处,如果再次引用,将抛出
NullPointerException
说明
- 消除过期引用最好的方法是: 让 包含该引用的变量结束期生命周期.(自然发生).
- 但上面的例子中显然
不是result
来管理elements[size]
生命周期,而是外层类Stack
提醒
内存泄漏来源
- 一般如果类自己管理内存,就需要警惕内存泄漏问题.
- 另一个常见来源是
缓存
.解决办法:使用WeakHashMap
.[常见的迷惑 就是 不知道缓存是否还有意义(生命周期).] - 最后,就是监听器和其他回调.解决办法: 使用弱引用(
Weak reference
)
7.避免使用终结方法
终结方法的缺点
终结方法(finalizer)是不可预测的,危险的,一般也是不必要的.根据经验,应尽量避免使用终结方法
JVM
会延迟执行 终结方法.导致从对象不可达到终结方法执行时间不可控.Java
语言规范不仅不保证终结方法及时执行,而且不保证他们会被执行.
不能依赖
终结方法
来更新重要的持久状态
.
System.gc
和System.runFinalization
只是增加了终结方法被执行的机会.
System.runFinalizersOnExit
和Runtime.runFinalizersOnExit
是声称可保证终结方法一定被执行,因有严重缺陷而废弃/
- 使用终结方法,有一个严重的
Severe
性能损失.
终结方案:
- 提供一个
显示的终止方法
.在对象不可用的时候,调用即可.典型例证:InputStream
,OutputStream
… - 显示的终止方法,通常与
try-finally
结合使用,以确保及时执行.
Foo foo = new Foo();
try{
//...
}finally{
foo.terminate();//
}
终结方法的好处
- 当 所有者 忘记调动显示 终结方法时,终结方法可以充当
安全网
(释放关键资源总比一点都不释放要好) - 第二种用途 与
java本地对等体
(native peer
) 相关.[本地对等体是一个本地对象,普通对象通过本地方法委托给一个本地对象].本地对等体不是一个普通对象,垃圾回收器并不知道它.当它的Java对等体被回收的时候,它不会被回收. - 终止方法链并不会被自动执行,如果
子类有并覆盖了
终结方法.就必须手动调用父类的终结方法
@Override protected void finalize() throws Throwable {
try {
//即使子类抛出异常,父类的终结方法也会执行
//finalize sub class
} finally {
super.finalize();
}
}
提醒
要防止忘记调用超类的终结方法,可以使用一个终结方法守卫者
,如下:
public class Foo{
private final Object finalizer = new Object(){
@Override protected void finalize() throws Throwable {
Foo.this.finalize();
}
}
}
小结
- 除非作为安全网,或者终止非关键的本地资源,否则不要使用本地方法/
- 既然使用了
终结方法
,就必须调用父类的终结方法. - 如果要把
终结方法
与公有的非 final类
关联起来,需要使用终结方法守卫者