1. 静态工厂方法代替构造器
简介
获取类的实例,除了提供公有的构造器外,还可以使用静态工厂方法
静态工厂方法 提供实例,不同于设计模式中的 工厂方法模式,简单示例:
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
优势:
相对于构造器,静态工厂方法有如下优势
- 他们有名称.如:
//不需要用参数列表来表达语义,针对需要多个相同签名的构造器(调整构造器参数顺序也可达到目的)
public boolean isProbablePrime(int certainty) {
if (certainty <= ) {
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
:
Builder
示例:NutritionFacts.java
优点:
既保持了重叠构造器那样的安全性,也保证了 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 = L;//这里每次都会构造一个实例,多创建了2^31个实例.
for (long i = ; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
小结
- 这里并不是说创建对象是十分昂贵的,如果能提高程序的清晰性,简洁性,多创建通常是好事.
- 除非
中的对象是重量级的,否则无需维护自己的对象池.Object pool
6.消除过期对象引用
示例:
//class Stack
public Object pop(){
if(size ==){
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本地对等体
) 相关.[本地对等体是一个本地对象,普通对象通过本地方法委托给一个本地对象].本地对等体不是一个普通对象,垃圾回收器并不知道它.当它的Java对等体被回收的时候,它不会被回收.native peer
- 终止方法链并不会被自动执行,如果
终结方法.就必须手动调用子类有并覆盖了
父类的终结方法
@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类
终结方法守卫者