整理Effective Java書中Item 29: Favor generic types心得筆記

主旨

本篇就是實際教你怎麼完成泛型的教學

範例

先從一個基本的物件導向堆疊的程式碼來說明,其中elements目前是以Object來做宣告,當我們呼叫pop()取得裡面的資料時必須要進行cast轉型,這個轉型動作在運行環境中是有可能發生ClassCastException的隱憂。

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();
        Object result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

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

開始針對上面程式碼進行調整成泛型化,首先第一步是在class的部分增加一個或多個參數類型public class Stack<E>,通常會取E這個字表示集合的資料類型,下一步就可以把Object都替換成E。

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new E[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    } 
    
    // no changes in isEmpty or ensureCapacity
}

這時候的程式碼會出現錯誤告訴你elements = new E[DEFAULT_INITIAL_CAPACITY];是不合法的沒辦法這樣宣告。這時候再繼續調整。

方法 1 在建構子的地方進行轉型,並加上消除unchecked的警告

@SuppressWarnings("unchecked")
public Stack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

方法 2 將elements宣告由 E[] 改為 Object[] ,並且在pop取出資料的地方進行轉型,並加上消除unchecked的警告

public class Stack<E> {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        @SuppressWarnings("unchecked")
        E result = (E) elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    } 
    
    // no changes in isEmpty or ensureCapacity
}

兩種方法都是可行的,但是方法1造成heap pollution的問題讓工程師會偏向使用方法2的技術(JDK原始碼也是)

小結

主要還是鼓勵盡可能使用泛型取代強制轉型更安全。