第1条:考虑使用静态工厂方法代替构造器
获取类实例的两种方法:公有的构造器、公有的静态工厂方法返回类的实例。
静态工厂方法的优势:
1.它们有名称
当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且慎重的选择名称以便突出它们之间的区别。
2.不必每次调用它们的时候都创建一个新对象
可以避免创建不必要的重复对象。这种方法类似于享元模式,如果程序经常请求创建相同的对象,可以使用静态工厂方法。
创建单例的时候,也用到静态工厂方法。
3.它们可以返回原返回类型的任何子类型的对象
这比较适用于基于接口的框架。API可以返回各种子对象,同时又不会使对象的类变成公有的。这样的API变得非常整洁。比如java.util.Collections。客户端永远不知道也不关心他们从工厂方法中得到的对象的类,而且在后续的版本中发生变化也不会对客户端造成影响。
4.在创建参数化类型实例的时候,它们使代码变得更加简洁
//调用参数化类的构造器时,必须要提供类型参数
Map<String, List<String>> m = new HashMap<String, List<String>>();
//工具类,提供静态工厂方法创建参数化类型实例
public static <K,V> HashMap<K,V> newInstance(){
return new HashMap<K,V>();
}
Map<String, List<String>> m = HashMap.newInstance();
静态工厂方法的缺点:
1.类如果不含公有的或者受保护的构造器,就不能被子类化。
不能被子类化就是不能被继承。
2.静态工厂方法与其他的静态方法实际上没有任何区别。
第2条:遇到多个构造器参数时要考虑用构造器
遇到参数比较多时,一般的构造器(重叠构造器),首先提供一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,以此类推……重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难写,并且较难阅读。
//重叠构造器的代码示例
public class NutritionFacts {
private final int servingSize; //(ml)
private final int servings; //(per container)
private final int calories; //
private final int fat; //(g)
private final int sodium; //(mg)
private final int carbohydrate; //(g)
public NutritionFacts (int servingSize,int servings) {
this(servingSize,servings,0);
}
public NutritionFacts (int servingSize,int servings,int calories) {
this(servingSize,servings,calories,0);
}
public NutritionFacts (int servingSize,int servings,int calories,int fat) {
this(servingSize,servings,calories,fat,0);
}
public NutritionFacts (int servingSize,int servings,int calories,int fat,int sodium) {
this(servingSize,servings,calories,fat,sodium,0);
}
public NutritionFacts (int servingSize,int servings,int calories,int fat,int sodium,int carbohydrate) {
this.servingSize=servingSize;
this.servings=servings;
this.calories=calories;
this.fat=fat;
this.sodium=sodium;
this.carbohydrate=carbohydrate;
}
}
第二种方式,即JavaBeans模式。只提供一个无参的构造函数。然后调用setter方法来设置参数。在构造过程中,JavaBean可能处于不一致的状态,类无法通过检验参数的有效性来保证一致性。
public class NutritionFacts {
private int servingSize=-1; //(ml)
private int servings=-1; //(per container)
private int calories=0; //
private int fat=0; //(g)
private int sodium=0; //(mg)
private int carbohydrate=0; //(g)
public NutritionFacts () {}
public void setServingSize(int servingSize) {this.servingSize = servingSize;}
public void setServings(int servings) {this.servings = servings;}
public void setCalories(int calories) {this.calories = calories;}
public void setFat(int fat) {this.fat = fat;}
public void setSodium(int sodium) {this.sodium = sodium;}
public void setCarbohydrate(int carbohydrate) {this.carbohydrate = carbohydrate;}
}
第三种方式就是Builder模式。builder像个构造器一样,可以对其参数强加约束条件。build方法可以检验这些约束条件。其实Builder就是一个JavaBean,只不过增加了一个build方法来检查参数的有效性。
public class NutritionFacts {
private int servingSize=-1; //(ml)
private int servings=-1; //(per container)
private int calories=0; //
private int fat=0; //(g)
private int sodium=0; //(mg)
private int carbohydrate=0; //(g)
public static class Builder{
//Required parameters
private final int servingSize;
private final int servings;
//Optional parameters - initialized to default values
private int calories =0;
private int fat =0;
private int carbohydrate =0;
private int sodium =0;
public Builder(int servingSize,int servings){
this.servingSize=servingSize;
this.servings=servings;
}
public Builder calories(int calories) {
this.calories = calories;
return this;
}
public Builder fat(int fat) {
this.fat = fat;
return this;
}
public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
return this;
}
public Builder sodium(int sodium) {
this.sodium = sodium;
return this;
}
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder){
servingSize=builder.servingSize;
servings=builder.servings;
calories=builder.calories;
fat=builder.fat;
sodium=builder.sodium;
carbohydrate=builder.carbohydrate;
}
}
第3条:用私有构造器或者枚举类型强化Singleton属性
1.饿汉式(类加载时创建实例)
//final公有的静态成员
public class Elvis01 {
public static final Elvis01 INSTANCE = new Elvis01();
private Elvis01(){ //私用构造器仅被调用一次
if(INSTANCE != NULL){
throw new SomeException();
}
......
}
public void leaveTheBuilding(){
System.out.println("Elvis01 leaveTheBuilding");
}
}
客户端通过反射机制调用私有构造器,将构造器修改成可访问的,从而创建出另一个实例。所以可以在创建第二个实例的时候抛出异常。
2.饿汉变种(提供静态方法获取实例)
//公有的成员是个静态工厂
public class Elvis02 implements Serializable{
private static final Elvis02 INSTANCE = new Elvis02();
private Elvis02(){
}
public void leaveTheBuilding(){
System.out.println("Elvis02 leaveTheBuilding");
}
public static Elvis02 getInstance(){
return INSTANCE;
}
}
3.懒汉式
public class Elvis04 implements Serializable{
private Elvis04 instance;
private Elvis04(){
}
public Elvis04 getInstance(){
if(instance == null){
instance = new Elvis04();
}
return instance;
}
}
这是线程不安全的。
上述三种方式,在序列化的时候都一些额外的工作,否则没有办法保证单例,因为每次反序列一个序列化的实例的时候,都会创建一个新的实例。
必须声明所有的实例域都是瞬时的(transient),并提供一个readResolve方法。
private Object readResolve(){
return INSTANCE;
}
4.枚举单例
public enum Elvis03 implements Serializable{
INSTANCE;
public void leaveTheBuilding(){
System.out.println("Elvis03 leaveTheBuilding");
}
}
5.静态内部类
实现了懒加载,并且是线程安全的。
利用类加载机制,实现了线程安全:客户端代码调用了getInstance时,JVM加载SingletonHolder,初始化静态成员,从而实例化了instance。
public class Elvis05 implements Serializable{
private Elvis05(){
}
public Elvis05 getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder{
private static Elvis05 instance = new Elvis05();
}
public void leaveTheBuilding(){
System.out.println("Elvis05 leaveTheBuilding");
}
}
第4条:通过私有构造器强化不可实例化的能力
有些类不希望被实例化,比如说一些工具类:java.util.Collections。
在缺少显式构造器的情况下,编译器会自动提供一个缺省的公有的无参构造器。这样的类仍然可能被实例化。
要写类不能被实例化,只要让这个类只提供一个私有构造器(private),就不能在外部被实例化。
这种做法使得这个类不能被子类化,因为所有的构造器都会显式或则隐式地调用超类构造器,在这种情况下,子类就没有可访问的超类构造器可调用了。
第5条:避免创建不必要的对象
最好能重用对象,而不是每次需要的时候就创建一个相同功能的新对象。
如果对象是不可变的,它始终可以被重用。
String s = new String("stringette")//每次都会创建一个新的实例
String s = “stringette” //重用同一个对象
除了重用不可变的对象之外,也可以重用那些一直不会被修改的可变对象。
//书上的一个坑
public static void main(String[] args){
Long sum = 0L;
for(long i = 0 ; i < Integer.MAX_VALUE; i++){
sum += i;
}
System.out.println(sum);
}
变量sum被声明为Long,每次执行sum += i的时候都要自动装箱,创建了很多不必要的Long对象。要优先使用基本类型而不是装箱基本类型,当心无意识的自动装箱。
第6条:消除过期的对象引用
Java虽然有垃圾回收机制,但是并不意味着你不用手动去管理内存。
只要类是自己管理内存,就应该警惕内存泄漏的问题。
下面就是内存泄漏常见的三种情形:
//简单的栈实现,从栈中取出一个元素
public Object pop(){
if(size == 0){
throw new EmptyStackException();
}
return elements[--size];
}
这段程序存在着“内存泄漏”。如果一个栈先是增长的,然后再收缩。那么,从栈中弹出来的对象将不会被当做垃圾回收。因为栈内部维护着对这些对象的“过期引用”。
public Object pop(){
if(size == 0){
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
内存泄漏的另一个常见的来源是缓存。一旦你把对象引用放到缓存中,他就很容易被遗忘掉,从而使它不再有用很长时间内仍然留在缓存中。
可以使用WeakHashMap,自动清除没有被外部引用的键值。(缓存本身就是为了提高数据的读取速度,如果在缓存中没有读到数据,再去内存中找。这并不会影响程序的正确性,所以WeakHashMap可以加快数据读取速度,也能够避免出现内存泄漏,因为被弱引用关联的对象只能生存到下一次垃圾收集发生之前。)
LinkedHashMap构建LRU缓存。
第三个常见的来源:监听器和其他回调。客户端通过API注册了回调,却没有显式地取消注册,导致对象积聚。
最佳方法是只保存回调的弱引用。
第7条:避免使用终结方法
终结方法只会被调用一次。
大致描述一下finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖或者finalize方法已经被执行过,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。(可对着下面的代码看)
上述流程存在一个严重问题就是将fianlize方法交给一个低优先级线程执行,因此不能保证被及时地执行,甚至根本不会保证被执行。
替代方法就是提供一个显式的终止方法。比如InputStream、OutputStream的close方法以及Timer的cancel方法。
显式的终止方法通常与try-finally结构结合起来使用,以确保及时终止。即使有异常抛出,finally块也始终会执行,因此终止方法肯定会被执行。
对象再生问题:finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的。
public class GC {
public static GC SAVE_HOOK = null;
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new GC();
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (null != SAVE_HOOK) { //此时对象应该处于(reachable, finalized)状态
System.out.println("Yes , I am still alive");
} else {
System.out.println("No , I am dead");
}
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (null != SAVE_HOOK) {
System.out.println("Yes , I am still alive");
} else {
System.out.println("No , I am dead");
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("execute method finalize()");
SAVE_HOOK = this;
}
}
代码执行结果
execute method finalize()
Yes , I am still alive
No , I am dead
终结方法有两种合理用途:
1.终结方法可以充当安全网,如果对象的所有者忘记调用了显式的终止方法,那么终结方法可以充当安全网,迟一点释放资源总比不释放资源要好。同时输出日志提示用户去调用显式的终止方法。
2.终止非关键的本地资源。如果是关键资源,必须调用终止方法。