優(yōu)雅地創(chuàng)建和銷毀對象

創(chuàng)建和銷毀對象概述

  • 何時以及如何創(chuàng)建對象

  • 何時以及如何避免創(chuàng)建對象

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

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

一.考慮用靜態(tài)工廠方法代替構造器

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

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

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

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

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

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

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

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

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 {
    //對象的必選參數(shù)
    private final int servingSize;
    private final int servings;
    //對象的可選參數(shù)的缺省值初始化
    private int calories = 0;
    private int fat = 0;
    private int carbohydrate = 0;
    private int sodium = 0;
    //只用少數(shù)的必選參數(shù)作為構造器的函數(shù)參數(shù)
    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 之前,實現(xiàn) Singleton 由兩種方法。這兩種方法都要把構造器保持為私有的,并導出公有的靜態(tài)成員,以便允許客戶端能夠訪問該類的唯一實例。
1.將構造函數(shù)私有化,直接通過靜態(tài)公有的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 起,實現(xiàn) Singleton 還有第三種方法。只需編寫一個包含單個元素的枚舉類型。單類型的枚舉類型是實現(xiàn)單利模式的最佳方法。

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

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

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

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

五.避免創(chuàng)建不必要的對象

String s = new String("stringette");

String s = "stringette";

比較這兩行代碼,上面的語句每次執(zhí)行的時候都會創(chuàng)建一個新的 String 實例,但是這些創(chuàng)建對象的動作全都是不必要的。而下面的語句只用了一個 String 實例,而不是每次執(zhí)行的時候都創(chuàng)建一個新的實例。由于 String 被實現(xiàn)為不可變對象,JVM 底層將其實現(xiàn)為常量池,所有值等于"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 類只是在初始化的時候創(chuàng)建 Calender、TimeZone 和 Date 實例一次,而不是在每次調用 isBabyBoomer 方法時都創(chuàng)建一次它們。如果該方法會被頻繁調用,效率的提升將會極為顯著。

六.消除過期對象的引用

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

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

七.避免使用終結方法

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

終止非關鍵的本地資源

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

終結方法不能保證會被及時地執(zhí)行

不應該依賴終結方法來更新重要的持久狀態(tài)

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

總結

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容