【Java基礎】建立和銷燬物件

cryAllen發表於2016-06-22

Num1:考慮用靜態工廠方法代替構造器

對於類而言,常見的方法是提供一個公有的構造器,但其實還有一種方法叫做靜態工廠方法(static factory method),它只是一個返回類的例項靜態方法。

目前比較流行的規範是把靜態工廠方法命名為valueOf或者getInstance

valueOf:該方法返回的例項與它的引數具有同樣的值,例如:

Integer a=Integer.valueOf(100); //返回取值為100的Integer物件

從上面程式碼可以看出,valueOf()方法能執行型別轉換操作,在本例中,把int型別的基本資料轉換為Integer物件。

getInstance:返回的例項與引數匹配,例如:

Calendar cal=Calendar.getInstance(Locale.CHINA); //返回符合中國標準的日曆

優勢:

  • 靜態工廠方法與構造器不同的第一大優勢在於,他們有名稱,更有可讀性。
  • 靜態工廠方法與構造器不同的第二大優勢在於,不必每次呼叫它們的時候都建立一個新物件。
  • 靜態工廠方法與構造器不同的第三大優勢在於,它們可以返回原返回型別的任何子型別的物件。
  • 靜態工廠方法與構造器不同的第四大優勢在於,在建立引數化例項的時候,它們使程式碼變得更加簡潔。

缺點:

  • 類如果不含公有的或者受保護的構造器,就不能被子類化。
  • 它們與其他的靜態方法實際上沒有任何區別。

Num2:遇到多個構造器引數時要考慮用構造器

一般有以下三種構造器的方式

  • 重疊構造器模式
  • JavaBeans模式
  • Builder模式

重疊構造器模式

public class NutritionFacts {
    private final int servingSize; // (mL) required
    private final int servings; // (per container) required
    private final int calories; // optional
    private final int fat; // (g) optional
    private final int sodium; // (mg) optional
    private final int carbohydrate; // (g) optional

    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;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
    }
}

JavaBeans模式

在這種模式下,呼叫一個無參構造器來建立物件,然後呼叫setter方法來設定每個必要的引數。如下:

public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize = -1; // Required; no default value
    private int servings = -1; // "     " "      "
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public NutritionFacts() {
    }

    // Setters
    public void setServingSize(int val) {
        servingSize = val;
    }

    public void setServings(int val) {
        servings = val;
    }

    public void setCalories(int val) {
        calories = val;
    }

    public void setFat(int val) {
        fat = val;
    }

    public void setSodium(int val) {
        sodium = val;
    }

    public void setCarbohydrate(int val) {
        carbohydrate = val;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts();
        cocaCola.setServingSize(240);
        cocaCola.setServings(8);
        cocaCola.setCalories(100);
        cocaCola.setSodium(35);
        cocaCola.setCarbohydrate(27);
    }
}

Builder模式

不直接生成想要的物件,而是讓呼叫者利用所有必要的引數呼叫構造器,得到一個builder物件,然後客戶端在builder物件上呼叫類似於setter的方法,來設定每個相關的可選引數,最後客戶端呼叫無參的build方法來生成一個不可變的物件。

public 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 {
        // 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 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) {
        servingSize = builder.servingSize;
        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();
    }
}

小結

與構造器相比,builder模式的優勢在於,builder可以有多個可變的引數,構造器就像方法一樣,只能有一個可變引數。總之,如果類的構造器或靜態工廠中具有多個引數,設計這種類的時候,builder模式就是一種不錯的選擇。

Num3:用私有構造器或者列舉型別強化Singleton屬性

Singleton簡單的說就是僅僅被例項化一次的類。實現Singleton有三種方式。

Field方式

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

Method方式

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

    private Elvis() {
    }

    public static Elvis getInstance() {
        return INSTANCE;
    }
  
    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.getInstance();
        elvis.leaveTheBuilding();
    }
}

特別要說明下,如果要使得類支援可序列化,僅僅加上implements Serializable是不夠的,需要在方法里加上這麼一個方法。

    private Object readResolve() {
        // Return the one true Elvis and let the garbage collector
        // take care of the Elvis impersonator.
        return INSTANCE;
    }

列舉類方式

public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

Num4:消除過期的物件引用

所謂過期引用:是指永遠也不會再被解除的引用。在支援垃圾回收的語言中,記憶體洩露是很隱蔽的,如果一個物件引用被這個物件無意識地保留起來,那麼,垃圾回收機制不僅不會處理這個物件,而且也不會處理被這個物件所引用的所有其他物件,從而對效能造成潛在的重大影響。

那麼該如何修復呢,很簡單:一旦物件引用已經過期了,只需要清空這些引用即可。

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

一般而言,只要類是自己管理記憶體,程式設計師就應該警惕記憶體洩露問題。

記憶體洩露的另一個常見問題是快取,所以一般可以弱引用weak reference代表快取,當快取過期後,它們會自動被刪除,記住快取項的生命週期是外部引用而不是值引用的。

記憶體洩露的第三個常見來源是監聽器和其他回撥。如果你實現了一個API,客戶端在這個API中註冊回撥,卻沒有顯式地取消註冊,那麼除非採取某些動作,否則就會積聚,確保回撥立即被當作垃圾回收的最佳方法是隻儲存它們的弱引用。

相關文章