1. 程式人生 > >java.util.AbstractCollection學習筆記

java.util.AbstractCollection學習筆記

前言

由今天開始,正式開始學習java.util包中的collection介面,及其相關類,並在該部落格賬號中進行更新,儘量每天一更,如果在部落格中存在錯誤,歡迎各位在留言區進行指證,相互交流,共同進步。

今天將從基本的AbstractCollection類開始學習,選擇這個類的原因是:這個類提供了collection的實現類應該具有的基本方法,具有一定的普適性,可以從大局上了解collection實現類的主要功能。

繼承結構

建構函式

在看程式碼的時候發現一件很奇怪的事情,抽象類AbstractCollection居然有protected建構函式,程式碼如下:

protected
AbstractCollection() { }

經過查資料得出如下結論:

  1. 首先,抽象類不能例項化是絕對正確的,因此抽象類中並不能包含public的構造方法;
  2. 抽象類protected構造方法會被隱性呼叫,因此並不一定在其子類的構造方法中顯示呼叫super(),雖然對於AbstractCollection而言是建議這麼做;
  3. 抽象類的protected構造方法可以用於初始化類中的某些屬性,避免一場資訊的出現出現;

    為了驗證以上三點,寫出測試程式碼如下:

 public abstract class AbstractClass {
        private int m;
        protected
AbstractClass() { } protected AbstractClass(int n) { m = n; } public int getM() { return m; } public void setM(int m) { this.m = m; } } public class TestExtendClass extends AbstractClass { public TestExtendClass
() { super(12); } public static final void main(String[] args) { TestExtendClass testExtendClass = new TestExtendClass(); System.out.println("hello world " + testExtendClass.getM()); } } hello world 12

當TestExtendClass並沒有顯式呼叫super()時,程式仍能夠正常進行,呼叫super(12)時,可以執行AbstractClass的建構函式,從而對其中的屬性進行初始化操作。

抽象函式

在AbstractCollection的註釋文件中提到:

To implement an unmodifiable collection, the programmer needs only to extend this class and provide implementations for the iterator and size methods.(The iterator returned by the iterator method must implement hasNext and next.)
To implement a modifiable collection, the programmer must additionally override this class's <tt>add</tt> method (which otherwise throws an UnsupportedOperationException), and the iterator returned by the iterator method must additionally implement its remove method.

由此可以看出,AbstractCollection將其實現類分成兩種,一種為只讀集合,另一種為可修改集合。

在只讀集合中,只需要實現AbstractCollection中的iterator函式和size函式即可,其它的函式可以維持不變(在對效能沒要求的前提下),這保證了實現類只需要少量的工作,便可以將集合的功能基本實現。

而對於可修改集合,AbstractCollection要求不僅需要實現其中的兩個抽象方法,還需要實現add方法,並保證iterator函式返回的迭代器中實現了remove方法。

對於非抽象演算法,AbstractCollection的建議為:如果有更加高效的實現方法,子類可以將其重寫(override),建議原文如下:

The documentation for each non-abstract method in this class describes its implementation in detail.  Each of these methods may be overridden if the collection being implemented admits a more efficient implementation.

非抽象函式

boolean isEmpty():判斷集合是否為空集合,基本演算法為判斷size()函式的返回值是否大於0;

boolean contains(Object o):通過迭代器遍歷連結串列,判斷集合中是否包含某一元素,對於特殊null物件,採取的是==運算子,對於普通物件則呼叫equals()函式判斷是否相等;

public Object[] toArray():這個函式的地位比較特殊,扮演者將Collection轉化為陣列的角色,因此將進行詳細分析,其原始碼與呼叫的相關程式碼如下:

public Object[] toArray() {
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext())
                return Arrays.copyOf(r, i);
            r[i] = it.next();
        }
        return it.hasNext() ? finishToArray(r, it) : r;
    }

private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;
        while (it.hasNext()) {
            int cap = r.length;
            if (i == cap) {
                int newCap = cap + (cap >> 1) + 1;
                // overflow-conscious code
                if (newCap - MAX_ARRAY_SIZE > 0)
                    newCap = hugeCapacity(cap + 1);
                r = Arrays.copyOf(r, newCap);
            }
            r[i++] = (T)it.next();
        }
        // trim if overallocated
        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError
                ("Required array size too large");
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

在函式toArray()中,首先根據collection當前的大小初始化一個數組,並根據集合大小設定期望的元素個數。而後使用迭代器依次遍歷集合中的元素,如果發現集合的元素自第i個起均被刪除,則直接返回r中的前i個元素。
如果發現在開始轉化後,集合中插入了新的元素,則會進入:擴容+複製的迴圈。其中擴容部分將陣列容量擴充套件到原來的1.5倍。當複製過程中,陣列容量再次填滿時,則又進行擴容。最後返回陣列中所有有效的元素。
注意:該演算法的複製並不是100%準確的,其只能保證其陣列中元素的個數與集合迭代器遍歷的元素個數相同,且順序相同,而不是保證該陣列中元素與集合元素相同。

<T> T[] toArray(T[] a)將集合複製到指定的陣列中。

public <T> T[] toArray(T[] a) {
        // Estimate size of array; be prepared to see more or fewer elements
        int size = size();
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array
                  .newInstance(a.getClass().getComponentType(), size);
        Iterator<E> it = iterator();

        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) { // fewer elements than expected
                if (a == r) {
                    //陣列a的長度大於集合中期望的元素個數,此時a與r等價
                    r[i] = null; // null-terminate
                } else if (a.length < i) {
                    // 陣列a的長度小於集合中期望的元素個數
                    // 並且陣列a的長度小於集合中當前已經轉化的元素個數
                    return Arrays.copyOf(r, i);
                } else {
                    //陣列a的長度小於集合中期望的元素個數
                    //並且陣列a的長度大於等於當前已經轉化的元素個數,則將r中的有效元素複製到a中
                    System.arraycopy(r, 0, a, 0, i);
                    if (a.length > i) {
                        a[i] = null;
                    }
                }
                return a;
            }
            r[i] = (T)it.next();
        }
        // more elements than expected
        return it.hasNext() ? finishToArray(r, it) : r;
    }

該函式要求使用者指定陣列地址。但是要注意:當陣列長度大於集合大小時,該函式將基於原陣列進行修改,而當陣列長度小於集合大小時,該函式將根據集合大小申請一個新的陣列。因此保險做法為,當呼叫該方法是,將原陣列的地址覆蓋。即:a=toArray(a)。為了驗證該猜想,編寫測試程式碼如下:


public class App {

    public static void main(String[] args) {
        Long[] a = {1L, 2L, 3L, 4L};

        List<Long> list = new ArrayList<Long>();
        list.add(4L);
        list.add(5L);

        System.out.println(a);
        for(int i = 0 ; i < a.length ; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();

        list.toArray(a);
        System.out.println(a);
        for(int i = 0 ; i < a.length ; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();

        Long[] b = {1L};
        System.out.println(b);
        for(int i = 0 ; i < b.length ; i++) {
            System.out.print(b[i] + " ");
        }
        System.out.println();

        list.toArray(b);
        System.out.println(b);
        for(int i = 0 ; i < b.length ; i++) {
            System.out.print(b[i] + " ");
        }
        System.out.println();

        b=list.toArray(b);
        System.out.println(b);
        for(int i = 0 ; i < b.length ; i++) {
            System.out.print(b[i] + " ");
        }

        System.out.println();
    }
}

輸出結果如下:
[Ljava.lang.Long;@b81eda8
1 2 3 4 
[Ljava.lang.Long;@b81eda8
4 5 null 4 
[Ljava.lang.Long;@68de145
1 
[Ljava.lang.Long;@68de145
1 
[Ljava.lang.Long;@27fa135a
4 5 

boolean add(E e)向集合中新增一個元素。集合預設為只讀的,因此預設實現為丟擲一個UnsupportedOperationException。如果需要實現一個可讀寫集合,則需要重寫該方法。

boolean remove(Object o)刪除集合中的特定元素。集合通過迭代器在集合中查詢需要刪除的物件,如果存在,則呼叫迭代器的remove方法刪除該物件,並返回true,否則返回false。該方法要求迭代器必須實現remove方法,否則將丟擲UnsupportedOperationException

boolean containsAll(Collection<?> c)判斷該集合中是否包含集合c中的所有元素,是返回true,否則返回false;

boolean addAll(Collection<? extends E> c)將集合c中的所有元素新增到目標集合中。該方法要求重寫add方法。如果所有元素均新增成功,返回true,否則將丟擲異常資訊。

boolean removeAll(Collection<?> c)從目標集合中刪除集合c中包含的所有元素,要求與remove方法類似;

boolean retainAll(Collection<?> c)要求集合c非空。將目標集合與集合c取交集,並刪除目標集合中的其他元素;

void clear()通過迭代器遍歷目標集合中的每個元素,並呼叫迭代器的remove方法刪除元素;

String toString()將集合裝化為String,格式為:[value1, value2, value3, value4]