創建和銷燬對象 筆記

1.靜態工廠方法和構造器優缺點
使用靜態工廠方法的優點:
1.靜態工廠方法有方法名稱,構造器沒有隻有參數類型,有方法名稱的話可以詳細表明對象是做什麼的。
2.靜態工廠方法不必在每次調用他們都創建新對象,可以用單例模式,避免重複創建對象造成浪費。
3.靜態工廠方法可以返回原返回類型的任何子類型對象。基於接口。(瞭解但不知道怎麼使用)
4.靜態工廠方法在創建參數化類型實例的時候,會使代碼更加簡潔。(沒用過)
使用靜態工廠方法的缺點:
1.當類如果不含有共有公有的和受保護的構造器,就不能被實例化。
2.靜態工廠方法和其他靜態方法一樣。

使用中,切記直接考慮使用構造器,先考慮是否使用靜態構造方法。

2.使用構建器(當構造器參數過多和可變參數的時候,使用構建器builder)
爲什麼不使用構造器:
1.當參數過多的時候,使用構造器實例化的需要多個構造器,比如,2個參數構造的實例、5個參數構造的實例。使用者很難編寫,難以閱讀,當相鄰參數類型一致的話稍微不注意就錯誤
爲什麼不是使用JavaBeans模式(使用一個無參的構造器,然後使用setXXX方法):
1.很不安全,使用過程中可能JavaBean可能處於不一致的狀態。
使用構建器的缺點:
1.爲了創建對象必須先創建它的構建器,性能問題。代碼厄長。

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

private static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    public static Elvis getInstance() {
        return INSTANCE;
    }

使用final來修飾對象,不論是誰調用靜態工廠方法都會返回唯一的對象實例。

4.通過私有構造器強化不可實例化的能力(Math等)
1。有些工具類不希望自己被實例化,實例化對它沒有任何意義,也不希望自己是基類。如果當前類不包含顯式構造器(在類中聲明的構造器)的話,那麼編譯器會默認的給它生成共有無參的構造器。可以通過顯式的構建私有構造器,來強化類不可實例化。那麼對於被繼承的話,可以採用final來修飾當前類。不可被繼承。爲了防止反射調用私有構造器在類中被調用可以採用如下代碼:

// Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }

5.避免創造不必要的對象
舉個例子代碼如下:

public class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods omitted

    // DON'T DO THIS!
    public boolean isBabyBoomer() {
        // Unnecessary allocation of expensive object
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomEnd = gmtCal.getTime();
        return birthDate.compareTo(boomStart) >= 0
                && birthDate.compareTo(boomEnd) < 0;
    }
}

此代碼塊,中每次調用isBabyBoomer方法都會創建一個Calendar、一個TimeZone、兩個Date的實例,這是不必要的。
下面是修改後的代碼:

class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods

    /**
     * The starting and ending dates of the baby boom.
     */
    private static final Date BOOM_START;
    private static final Date BOOM_END;

    static {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_START = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_END = gmtCal.getTime();
    }

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

改進後的代碼只有在類初始化的時候創建Calendar、TimeZone、Date實例一次。並且BOOM_START和BOOM_END都是從局域變量改爲final作用域,這兩個對象顯然被當成常量使用(賦值一次就不會改變)。
當然有個小問題是如果不掉用isBabyBoomer方法的話,也會造成創建不必要的對象。

小心自動裝箱:

public class Sum {
    // Hideously slow program! Can you spot the object creation?
    public static void main(String[] args) {
        Long sum = 0L;
        Long startTime = System.currentTimeMillis();
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        long endTime = System.currentTimeMillis();

        System.out.println(sum+"time:"+(endTime-startTime)/1000);
    }
}

當sum類型是Long的時候。測試時間是10S,原因是大約構造了2^31次方的Long實例,自動裝箱了。
當採用long基本類型的時候:

public class Sum {
    // Hideously slow program! Can you spot the object creation?
    public static void main(String[] args) {
        long sum = 0L;
        Long startTime = System.currentTimeMillis();
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        long endTime = System.currentTimeMillis();

        System.out.println(sum+"time:"+(endTime-startTime)/1000);
    }
}

運行時間不到1S中。所以創建對象中要注意自動裝箱問題。

6.消除過期的對象引用
下面看一個錯誤的例子:

public class Stack {
    private Object[] elements;
    private int 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 Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * Ensure space for at least one more element, roughly doubling the capacity
     * each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

此段程序會發生內存泄漏,當棧先增長,再收縮,那麼從棧中彈出的對象將不會被當作垃圾回收,即使使用棧的程序不再引用這些對象,它們也不會被回收。這是因爲,棧內部維護這這些對象的過期引用。所謂的過期引用,是指永遠不會被解除的引用。凡是在elements數組的活躍部分(active portion)之外的引用都是過期的。活動部分是指elements中下標中小於size的元素(發生在棧先增長,再彈出元素的時候)。
在支持垃圾回收的語言中,內存泄漏是很隱蔽的(稱這類內存泄露爲”無意識的對象保留“)如果一個對象引用被無意識的保留起來,那麼垃圾回收機制不僅不會處理這個對象,而且也不會處理被這個對象所引用的其他所有對象。即使只有少量的幾個對象引用被無意識的保留下來,也會有許多對象被排除在垃圾回收機制外,從而對性能造成重大影響。

這類問題的修復方法很簡單:一旦對象引用已經過期,只需清空這些對象引用即可。對上例子的棧來說的話, 只要一個單元被彈出棧,指向它的引用就過期了,修改代碼如下:

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

消除過期引用的最好的方法是讓包含該引用的變量結束其生命週期。
緩存、監聽器、回調都容易發生內存泄漏。

7.避免使用終結方法(finalizer)
1.不能保證被及時執行。
2.可能會延遲的其實例回收過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章