天天看點

TIJ——第11章:Collection和Iterator

Java集合2:Collection與AbstractCollection

Java中Iterable和Iterator接口

了解java容器:iterator與collection,容器的起源

Java_集合—Collection和Iterator

Java中的collection集合類型總結

Collection和AbstractCollection

AbstractCollection實作了Collection接口,是一個抽象類,有兩個抽象方法:

public abstract Iterator<E> iterator();

public abstract int size();
           

Collection接口的其他方法,都有預設的實作;其中:

1:isEmpty()方法調用size()來判斷

2:contains()、containsAll()、toArray()方法是通過循環iterator()方法傳回的疊代器來查詢的;

3:remove()和removeAll()方法是通過iterator()方法傳回的疊代器提供的remove()方法來實作的;

4:add()方法預設是不支援的,addAll()方法是多次調用add()方法;

如果想要實作一個不可修改的Collection,隻需要繼承AbstractCollection并且實作兩個抽象方法即可;

如果想要實作可以修改的Collection,還需要重寫add()方法和iterator()方法傳回的疊代器實作remove()方法;

Collection介紹

 通過定義我們知道Collection表示一組對象,根據集合類型的不同,有的允許重複元素,有的是有序的,這個要看具體的子接口的實作情況。Collection接口中定義一些通用的方法。這些方法都比較基本而且使用都比較頻繁,是以我們需要對每一個方法都記錄,按方法的作用我們可以分為以下幾類:

  1. 添加

    共兩個方法,分别是add和addAll, 分别是接收一個對象和一個Collection對象。

  2. 删除

    共四個方法,remove, removeAll, retainAll,clear, 其中需要說的是retainAll,這個操作接受一個Collection作為參數,取兩個集合的交集。

  3. 查找

    共兩個方法, contains, containsAll, 判斷集合中是否有某個或某些元素

  4. 轉換

    共三個方法, toArray(), toArray(T t[])和iterator, 前兩個是把集合轉化為數組,另外一個是轉化為一個Iterator對象,可用于周遊,這個方法其實在其父接口Iterable中也有定義。

  5. 求大小

    共兩個方法, size()和isEmpty(), 分别是求長度和判斷集合是否為空。

  6. 比較

    共兩個方法,equals和hashCode,這兩個方法是從object中繼承過來的。

    以上共15個方法,大多數還是很好了解。需要重點關注的是轉換類的兩個方法,由于Collection繼承了Iterable,是以所有的collection都可以通過foreach的方式來調用,這是JDK1.5之後的一種文法糖。

    關于Collection的介紹就到這裡,下面接着看一下其直接骨架類AbstractCollection.

AbstractCollection介紹

雖然Collection中的方法很多,其不同子類型的表現也不一樣,但事實上這15個方法中有很多都是跟具體的子類沒有關系的,為了簡化具體Collection類的設計, JDK提供了一個抽象類AbstractCollection,對其中的大多數方法進行了實作。

方法的實作沒有必要依次去介紹,這裡主要介紹這個子類的一些特點及幾個重要方法的實作。

  1. 本類預設是不是可修改的,即不支援add,由于addAll依賴于add,是以addAll也是不支援的,要支援添加功能,就需要重寫這個add方法
  2. size,iterator這兩個方法沒有實作,是以要編寫自己的collection,需要實作這兩個方法即可。
  3. 其它所有方法都有實作,不過涉及到周遊的都依賴于iterator()傳回的疊代器,删除也依賴于疊代器提供的删除方法。
  4. 本類沒有對equals和hashCode進行重寫,但是對toString進行了重寫。

大部分方法的實作很容易了解,下面重點介紹一下toArray。

重點方法分析

Collection可以直接轉化為數組,本接口中有兩個方法,Object[] toArray()和 T[] toArray(T[] a),相信有些人和我一樣,在使用時會感覺到困惑,一是不知道使用哪個方法,二是不知道第二個方法的參數和傳回值之間有什麼關系,下面我們就來認真分析一下。

先看一下JDK對于這個方法的定義描述:The returned array will be “safe” in that no references to it are maintained by this collection. (In other words, this method must allocate a new array even if this collection is backed by an array). The caller is thus free to modify the returned array.

從這個描述我們可以知道toArray得到的數組跟原collection沒有任何關系,我們可以對數組的每個引用值做修改,而不會影響到原collection.這個看起來好像是多餘說明的,但是考慮到ArrayList其實就是基于數組實作的,那這個限制保證了即使是将ArrayList轉化為數組,那也應該是配置設定一個新數組,而不是傳回原來的數組。

好了,我們再看一下具體的代碼。

public Object[] toArray() {
        // Estimate size of array; be prepared to see more or fewer elements
        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;//collection的集合可能會變大
    }

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

上面是AbstractCollection的實作,可以看到對于toArray()來說,就是配置設定了一個等大空間的數組,然後依次對數組元素進行指派。

如果我們在單線程操作的情況下,collection集合大小不變,正常應該是執行到 return it.hasNext() ? finishToArray(r, it) : r; 這條語句結束,但考慮到在複制的過程中,collection的集合可能會有變化,可能是變大也可能是變小,是以方法增加了對這種情況的處理,這就是為什麼每次循環都要判斷是collection是否周遊完,以及最後再判斷collection是否變得更長,如果是的話,還需要重新再為array配置設定空間。

通常情況下,我們不會執行到hugeCapacity,但作為一個架構來說,這展現了設計時的嚴謹。

可以看到,toArray傳回的是一個Object數組,不能很好的展現collection中的元素類型,這樣collection的泛型就無法展現出優勢。是以,我們又有了第二個方法。個人當時在使用這個方法是,最大的疑惑就在于不知道這個參數應該怎麼傳,下面我們來看下具體的實作。

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) {
                    r[i] = null; // null-terminate
                } else if (a.length < i) {
                    return Arrays.copyOf(r, i);
                } else {
                    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能夠存儲則傳回a,如果不能再新配置設定。在看了這個之後,對于這新代碼strList.toArray(new String[0])相信就很容易了解了。

在看了這兩個方法後,我相信對于toArray的差別和使用就比較容易掌握了,個人建議還是使用第二種比較好一些,在參數的選擇上,要麼傳遞一個0長度的數組,要麼就傳遞一個與集合等長的數組,但考慮到集合的可變性,我們應該使用這個方法的傳回值,而不是直接使用參數數組。

五、總結

總的來說,Collection和AbstractCollection還是比較簡單,但隻有掌握了這兩個簡單的類,在學習後續的各種list和set的時候,我們才能更好的了解。