優雅地創建和銷毀對象

創建和銷毀對象概述

  • 何時以及如何創建對象

  • 何時以及如何避免創建對象

  • 如何確保對象適時地銷毀

  • 如何管理對象銷毀之前必須進行的各種清理動作

一.考慮用靜態工廠方法代替構造器

構造器是創建一個對象實例最基本也最通用的方法,大部分開發者在使用某個 class 的時候,首先需要考慮的就是如何構造和初始化一個對象示例,而構造的方式首先考慮到的就是通過構造函數來完成,因此在看 javadoc 中的文檔時首先關注的函數也是構造器。然而在有些時候構造器并非我們唯一的選擇,我們可以通過靜態類工廠的方式來創建 class 的實例,如

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

相比于構造器,靜態工廠方法的優勢
1.有意義的名稱
構造方法本身沒有名稱,不能確切的返回描述返回的對象,具有適當名稱的靜態工廠方法更容易被使用,產生的代碼也更容易被閱讀。
2.不必每次調用的時候創建一個新的對象
構造方法的每次調用都會重新創建一個新的實例,而靜態工廠方法可以預先構建好實例/實例緩存,進行重復利用,從而避免不必要的重復對象的創建。
3.能返回原返回類型的任何子類型的對象
構造方法只能返回類本身,而靜態方法可以返回它的子類,利用靜態使得方法更加靈活。
4.創建參數化實例的時候,它們使代碼變得更加簡潔

Map<String,List<String>> stringListMap = new HashMap<>();

由于 Java 在構造函數的調用中無法進行類型的推演,因此也就無法通過構造器的參數類型來實例化指定類型參數的實例化對象。然而通過靜態工廠方法則可以利用參數類型推演的優勢,避免了類型參數在一次聲明中被多次重寫所帶來的煩憂,見如下代碼:

public static <K,V> HashMap<K,V> newInstance() {
   return new HashMap<K,V>();
}
?```
//調用
```java
Map<String,List<String>> m = HashMap.newInstance();

二.遇到多個構造器參數時要考慮用構建器

當實例化一個類時,特別是有很多可選的參數,如果我們考慮使用寫很多不同參數的構造方法,就會使得可讀性變得很差。這個時候推薦 Builder 模式來創建這個帶有很多可選參數的實例對象。

class NutritionFacts {
  private final int servingSize;
  private final int servings;
  private final int calories;
  private final int fat;
  private final int sodium;
  private final int carbohydrate;
  public static class Builder {
    //對象的必選參數
    private final int servingSize;
    private final int servings;
    //對象的可選參數的缺省值初始化
    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 val) {
       calories = val;
       return this;
    }
    public Builder fat(int val) {
        fat = val;
        return this;
    }
    public Builder carbohydrate(int val) {
        carbohydrate = val;
        return this;
    }
    public Builder sodium(int val) {
        sodium = val;
        return this;
    }
    public NutritionFacts build() {
        return new NutritionFacts(this);
    }
 }
 private NutritionFacts(Builder builder) {
    servings = builder.servings;
    calories = builder.calories;
    fat = builder.fat;
    sodium = builder.sodium;
    carbohydrate = builder.carbohydrate;
  }
}
//使用方式
public static void main(String[] args) {
   NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100)
   .sodium(35).carbohydrate(27).build();
   System.out.println(cocaCola);
}

三.用私有構造器或枚舉類型強化 Singleton 屬性

在 Java1.5 之前,實現 Singleton 由兩種方法。這兩種方法都要把構造器保持為私有的,并導出公有的靜態成員,以便允許客戶端能夠訪問該類的唯一實例。
1.將構造函數私有化,直接通過靜態公有的final域字段獲取單實例對象:

public class Elvis {
   public static final Elvis INSTANCE = new Elvis();
   private Elivs() { ... } 
}       

2.通過公有域成員的方式返回單實例對象

public class Elvis {
   public static final Elvis INSTANCE = new Elvis();
   private Elivs() { ... }
   public static Elvis getInstance() { 
      return INSTANCE; 
 }
   public void leaveTheBuilding() { ... }
}

Java1.5 起,實現 Singleton 還有第三種方法。只需編寫一個包含單個元素的枚舉類型。單類型的枚舉類型是實現單利模式的最佳方法。

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

四.通過私有構造器強化不可實例化的能力

不可實例化是指當前的類只包含 靜態方法和靜態域的類。在 Java 中,只有當類不包含顯示的構造器時,編譯器才會生成缺省的構造器,因為只要讓類包含私有的構造器,它就不能被實例化。

public class BitmapUtils {
   private BitmapUtils() {
       throw new AssertionError();
   }
}

五.避免創建不必要的對象

String s = new String("stringette");

String s = "stringette";

比較這兩行代碼,上面的語句每次執行的時候都會創建一個新的 String 實例,但是這些創建對象的動作全都是不必要的。而下面的語句只用了一個 String 實例,而不是每次執行的時候都創建一個新的實例。由于 String 被實現為不可變對象,JVM 底層將其實現為常量池,所有值等于"stringette"的對象實例共享同一對象地址,而且還可以保證,對于所有在同一 JVM 中運行的代碼,只要他們包含相同的字符串字面常量,該對象就會被重用。
除了重用不可變的對象之外,也可以重用那些已知不會被修改的可變對象。

public class Person {
   private final Date birthDate;
   //判斷該嬰兒是否是在生育高峰期出生的。
   public boolean isBabyBoomer {
      Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
      c.set(1946,Calendar.JANUARY,1,0,0,0);
      Date dstart = c.getTime();
      c.set(1965,Calendar.JANUARY,1,0,0,0);
      Date dend = c.getTime();
      return birthDate.compareTo(dstart) >= 0 && birthDate.compareTo(dend) < 0;
   }
}

public class Person {
   private static final Date BOOM_START;
   private static final Date BOOM_END;
       
   static {
      Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
      c.set(1946,Calendar.JANUARY,1,0,0,0);
      BOOM_START = c.getTime();
      c.set(1965,Calendar.JANUARY,1,0,0,0);
      BOOM_END = c.getTime();
 }
   public boolean isBabyBoomer() {
      return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
   }
}

改進后的 Person 類只是在初始化的時候創建 Calender、TimeZone 和 Date 實例一次,而不是在每次調用 isBabyBoomer 方法時都創建一次它們。如果該方法會被頻繁調用,效率的提升將會極為顯著。

六.消除過期對象的引用

內存泄露:如果一個棧先是增長,然后收縮,那么,從棧中彈出來的對象是不會被當做垃圾回收的,即使使用棧的程序不再引用這些對象,它們也不會被回收。這是因為,棧內部維護著對這些對象的過期引用。由于過期引用的存在, GC 并不會去回收它們,所以需要我們手動清空這些引用。比如 :

public Object pop() {
   if(size==0) throw new EmptyStackException();
   Object result = elements[--size];
   elements[size] = null; //Eliminate obsolete reference
   return result;
}

七.避免使用終結方法

終結方法的好處
當對象的所有者忘記調用前面段落中建議的顯式終止方法時,終結方法可以充當“安全網”

終止非關鍵的本地資源

終結方法的缺點
終結方法(finalizer)通常是不可預測的,也是很危險的,一般情況下是不必要的。使用終結方法會導致行為不穩定、降低性能,以及可移植性問題。

終結方法不能保證會被及時地執行

不應該依賴終結方法來更新重要的持久狀態

使用終結方法有嚴重的性能損失

總結

以上是我對閱讀了《 Effective Java 》第一篇之后的一些總結,希望可以幫助大家寫出更優雅的代碼。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容