1. 程式人生 > >effective java中文版 第二章 建立和銷燬物件

effective java中文版 第二章 建立和銷燬物件

第1條:考慮用靜態工廠方法替代構造器
如下方法將boolean基本型別值轉換為了一個Boolean物件引用

public static Boolean valueOf(boolean b){
  return b ?Boolean.TRUE:Boolean.FALSE;
}

靜態工廠方法與構造器相比的第一大優勢:它們會有名稱
第二大優勢(重點):不必在每次呼叫它們的時候都建立一個新物件。靜態工廠方法能夠為重複的呼叫返回相同的物件,這樣有助於類總能嚴格控制在某個時刻哪些例項應該存在
第三大優勢:它們可以返回原返回型別的任何子型別的物件
第四大優勢在於:在建立引數化型別例項的時候,它們讓程式碼變得更加簡潔。例如HashMap提供了這個靜態工廠

  public static <K,V> HashMap<K,V> newInstance(){
     return new HashMap<K,V>();
  }
    

如下為靜態工廠方法的一些慣用名稱
1,valueOf 它實際上是型別轉換方法
2,getInstance 返回的例項通過方法的引數來描述
3,newInstance 確保返回的每個例項與其他例項不同

第2條 遇到多個構造器引數時要考慮用構建器
Bulilder模式:不直接生成想要的物件,而是讓客戶端利用所有必要的引數呼叫構造器得到一個builder物件。然後客戶端在builder物件上呼叫類似於setter的方法,來設定每個相關的可選引數。最後呼叫無參的build方法來生成不可變的物件。這個builder是它構建的類的靜態成員類。
下面為例項

public class Demo{
  private final int a;
  private final int b;
public static class Builder{
  private final int a;
  private final int b;
  
  public Builder(int a,int b){
     this.a = a;
     this.b = b;
}
public Demo bulid(){
  return new Demo(this);
}   
 }
 private Demo(Builder builder){
    a = builder.a;
    b = builder.b;
 }

}

如果類的構造器或者靜態工廠中有多個引數,設計這種類時Builder模式是個不錯的選擇。

第3條 用列舉類強化Singleton屬性

 public enum Elivis{
    INSTANCE;
    public void leaveTheBuilding(){....
    }
 }

這種方法更加簡潔,無償提供了序列化機制,防止多次例項化。

第4條 通過私有構造器強化不可例項化的能力
在我們日常開發工具類時,不希望被例項化,這樣的例項對它沒有任何意義。然而,在缺少顯示構造器的情況下,編譯器會自動提供一個公有的,無參的預設構造器。這種情況我們只要讓這個類包含私有構造器,它就不能被例項化了。
public class UtilityClass{
private UtilityClass(){

}
}

第5條 避免建立不必要的物件
直接上例子。。。

public class Person {
  private final Date birthDate;
  
  public boolean isBabyBoomer(){
   //Unnecessary allocation of expensive object 
    Calendar a = Calendar.getInstance(TimeZone.get("GMT"));
    a.set(....);
    Date startDate = a.getTime();
    a.set(...);
    Date endDate = a.getTime();
    return birthDate.compareTo(startDate) >= 0 &&
    birthDate.compareTo(boomEnd) < 0;    
  }
}

這個例項每次被呼叫的時候都會新建一個Calendar,一個TimeZone和兩個Date。這是不必要的。下面的版本用靜態的初始化器提升了效率

class Person{
   private final Date birthDate;
    
   private static final Date START_DATE;
   private static final Date END_DATE;
   
   static{
      Calendar a = Calendar.getInstance(TimeZone.get("GMT"));
      a.set(...);
      START_DATE = a.getTime();
      a.set(...);
      END_DATE = a.getTime();
   }

  public boolean isBabyBoomer(){
    return birthDate.compareTo(START_DATE) >= 0 &&
    birthDate.compareTo(END_DATE) < 0; 
  }
}

改進後的程式碼只在初始化的時候建立這些類的時候例項化一次,而不是每次呼叫isBabyBoomer的時候都建立這些例項。如果方法被頻繁的呼叫,將會顯著地提高效能。

java1.5發行版本中,有一種建立多餘物件的新方法,稱作自動裝箱,它允許程式設計師將基本型別和裝箱基本型別混用,按需要自動裝箱和拆箱。
上例子,哈哈哈

public static void main(String[] args){
  Long sum = 0L;
  for(long i = 0;i < Integer.MAX_VALUE;i++){
     sum += i;
  }
}

這段程式算出的答案是正確的,但是效率低。就因為變數sum被宣告為Long而不是long.
結論:要優先使用基本型別而不是裝箱基本型別,要當心無意識的自動裝箱。

第6條 消除過期物件的引用 (阿西吧。。這章沒怎麼看懂)
先上一個簡單的棧實現的例子

public class Stack{
   private object[] elements;
   private ine size = 0;
   private static final int DEFAULT_INITIAL_CAPACITY = 16;

   public Stack(){
      elements = new Object[DEFAULT_INITIAL_CAPACITY ];
   }
   public void push(Object e){
     ensureCapacity();
     elements [size++] = e;
   }
   
  public void pop(){
    if(size == 0){
       throw new EmptyStackException();
    }
    return elements[--size];
  }

  public void ensureCapacity(){
    if(elements.length == size){
       elements = Arrays.copyOf(elements,2*size + 1);
    }
  }
}

這段程式哪裡發生了記憶體洩漏呢?如果一個棧先是增長然後再收縮,那麼棧中彈出來的物件將不會被當作垃圾回收,即使程式不再使用這些物件。because棧內部維護著對這些物件的過期引用。如果一個物件引用被無意識的保留起來了,垃圾回收機制不僅不會回收這個物件,也不會處理被這個物件的所有其他物件。
修復方法:一旦物件引用過期,清空這些引用即可。
一般而言,只要類是自己管理記憶體, 程式設計師就該警惕記憶體洩漏問題。
。。。。。。(這章不好理解啊。。。。。先略。。。)

第7條 避免使用終結方法(阿西吧。。。只是理解了一部分。。。心塞)
終結方法的一個缺點在於不能保證會被及時的執行。
及時的執行終結方法正是垃圾回收演算法的一個主要功能。
結論:不應該依賴終結方法來更新重要的持久狀態。
使用終結方法有一個非常嚴重的效能損失。
如果類的物件中封裝的資源確實需要終止,這個時候只需要提供一個顯示的終結方法。並要求該類的客戶端在每個例項不再有用的時候呼叫這個方法。其中,該例項必須記錄下自己是否已經被終止。