天天看點

Java Collection Framework : Collection 接口

一、要點

  • Collection 接口
  • Collection 接口的預設實作:AbstractCollection
  • Optional operations(可選方法)

二、Collection 接口

1、JDK 對 Collection 接口的描述

  

Collection 接口是 Collection 層次結構中的根接口

。Collection 表示一組對象,這些對象也稱為 collection 的元素。一些 collection(List,Queue) 允許有重複的元素,而另一些(Set)則不允許。一些 collection (List,Queue)是有序的,而另一些(Set)則是無序的。

JDK 不提供此接口的任何直接實作:它提供更具體的子接口(如 Set 和 List)實作,這些子接口繼承自Collection 接口。此接口通常用來傳遞 collection(多态),并在需要最大普遍性的地方操作這些 collection

  所有通用的 Collection 實作類(通常通過它的一個子接口間接實作 Collection)應該提供

兩個“标準”構造方法:

一個是 void(無參數)構造方法,用于建立空 collection;另一個是帶有 Collection 類型單參數的構造方法,用于建立一個具有與其參數相同元素新的 collection。實際上,後者允許使用者複制任何 collection,以生成所需實作類型的一個等效 collection。盡管無法強制執行此約定(因為接口不能包含構造方法),但是 Java 平台庫中所有通用的 Collection 實作都遵從它。

  

此接口中包含的“破壞性”方法,是指可修改其所操作的 collection 的那些方法,如果此 collection 不支援該操作,則指定這些方法抛出 UnsupportedOperationException(可選操作)。

  

Collections Framework 接口中的很多方法是根據 equals 方法定義的。

  

Collections Framework 中的各個容器均是非線程安全的。

  

  Collection 接口的定義如下:

public interface Collection<E> extends Iterable<E> { ... }
           

            

Java Collection Framework : Collection 接口

2、行為

   java.util.Collection 接口是描述 Set 和 List 集合類型的根接口,下面給出了Collection 的所有行為(不包括從Object 繼承而來的方法),是以它們也刻畫了 Set 或 List 執行的所有行為(List 還有額外的功能)。Collection 的行為分類和詳細清單如下 :

     

Java Collection Framework : Collection 接口

      

                        表1. Collection 的操作

Function Introduction Note
boolean add(T) 将指定元素添加到集合中,若未将該元素添加到容器,傳回 false 可選操作
boolean addAll(

Collection<? extends T>

)
添加參數中的所有元素,隻要添加進任意元素則傳回 true 可選操作
Iterator iterator( ) 周遊容器中的元素(統一的通路、周遊方法,對外屏蔽了不同容器的差異性,疊代器模式) 依賴于具體實作
boolean remove(Object) 向容器移除指定元素,若移除動作發生,則傳回 true(若有重複對象,一次隻删除其中一個) 可選操作
boolean removeAll(

Collection<?>

)
向容器移除參數中所有元素,若有移除動作發生,則傳回 true 可選操作
Boolean retainAll(

Collection<?>

)
隻儲存參數中的元素,隻要Collection 發生改變,則傳回 true 可選操作
void clear() 移除容器中所有元素 可選操作
boolean contains(T) 若容器持有該元素,傳回 true 依賴于equals()
Boolean containsAll(

Collection<?>

)
若容器持有參數中的所有元素,傳回 true 依賴于equals()
object[] toArray() 傳回一個包含該容器所有元素的 Object 型數組 Array與Collection之間的橋梁,在 AbstractCollection 中提供預設實作,子類可以對它重寫
T[] toArray(T[] a) 傳回一個包含該容器所有元素的 Object 型數組,且傳回結果的運作時類型與參數數組 a 相同,而不單純是object Array與Collection之間的橋梁,在 AbstractCollection 中提供預設實作,子類可以對它重寫
int size() 傳回容器中元素數目 依賴于具體實作
boolean isEmpty() 容器為空,則傳回 true 依賴于size()

   注意到,

其中不包含随機通路所選擇的元素 get() 方法,因為 Collection 包含 Set,而 Set 是自己維護順序的。是以,若想通路、周遊 Collection 中的元素,則必須使用疊代器; 另外,這些方法的預設實作(AbstractCollection)均直接或間接使用了疊代器。

三、Collection 接口的預設實作:AbstractCollection

1、JDK 對 AbstractCollection 抽象類的描述

  

此抽象類提供 Collection 接口的骨幹實作,以最大限度地減少實作此接口所需的工作。

要實作一個不可修改的 collection,程式設計人員隻需擴充此類,并提供 iterator 和 size 方法的實作。(iterator 方法傳回的疊代器必須實作 hasNext 和 next。); 要實作可修改的 collection,程式設計人員必須另外重寫此類的 add 方法(否則,會抛出 UnsupportedOperationException),iterator 方法傳回的疊代器還必須另外實作其 remove 方法(AbstractCollection 的移除方法間接調用疊代器的移除方法)。按照 Collection 接口規範中的建議,程式設計人員通常應提供一個 void (無參數)和 Collection 構造方法。

    

Java Collection Framework : Collection 接口

  AbstractCollection 抽象類定義如下:

public abstract class AbstractCollection<E> implements Collection<E> { ... }
           

2、行為(對上述 Collection 接口中方法的實作情況)

  下面給出了抽象類 AbstractCollection 對 Collection 接口的實作情況:

public boolean add(E e) {
    throw new UnsupportedOperationException();
    }


   public boolean addAll(Collection<? extends E> c) {
    boolean modified = false;
    Iterator<? extends E> e = c.iterator();
    while (e.hasNext()) {
        if (add(e.next()))                  // 調用了上面定義的 add 方法
        modified = true;
    }
    return modified;
    }


    public abstract Iterator<E> iterator();               //未提供具體實作,将實作延遲到具體容器


    public boolean remove(Object o) {
    Iterator<E> e = iterator();
    if (o==null) {
        while (e.hasNext()) {
        if (e.next()==null) {
            e.remove();                 //内部使用了疊代器的 remove 方法,故具體容器的疊代器實作密切相關
            return true;
        }
        }
    } else {
        while (e.hasNext()) {
        if (o.equals(e.next())) {
            e.remove();
            return true;
        }
        }
    }
    return false;
    }


    public boolean removeAll(Collection<?> c) {
    boolean modified = false;
    Iterator<?> e = iterator();
    while (e.hasNext()) {
        if (c.contains(e.next())) {
        e.remove();                       //内部使用了疊代器的 remove 方法,故具體容器的疊代器實作密切相關
        modified = true;
        }
    }
    return modified;
    }


    public boolean retainAll(Collection<?> c) {
    boolean modified = false;
    Iterator<E> e = iterator();
    while (e.hasNext()) {
        if (!c.contains(e.next())) {
        e.remove();                       //内部使用了疊代器的 remove 方法,故具體容器的疊代器實作密切相關
        modified = true;
        }
    }
    return modified;
    }


    public void clear() {
    Iterator<E> e = iterator();
    while (e.hasNext()) {
        e.next();
        e.remove();                       //内部使用了疊代器的 remove 方法,故具體容器的疊代器實作密切相關
    }
    }


    public boolean contains(Object o) {
    Iterator<E> e = iterator();
    if (o==null) {
        while (e.hasNext())
        if (e.next()==null)
            return true;
    } else {
        while (e.hasNext())
        if (o.equals(e.next()))                 //内部調用了的 equals方法,故與 equals 方法實作密切相關
            return true;
    }
    return false;
    }


    public boolean containsAll(Collection<?> c) {
    Iterator<?> e = c.iterator();
    while (e.hasNext())
        if (!contains(e.next()))                //内部調用了的 contains 方法,故與 contains 方法實作密切相關
        return false;
    return true;
    }


    //此處省略兩個 toArray 方法的實作


    public abstract int size();                   //未提供具體實作,将實作延遲到具體容器


    public boolean isEmpty() {
    return size() == ;                      //内部調用了的 size 方法,故與 size 方法實作密切相關
    }
           

   對以上實作進行總結 :

  • 】:add, addAll 兩個方法的實作是可選的,此處均預設 UnsupportedOperationException ;
  • 】:iterator 未提供具體實作,将實作延遲到具體容器,其對外屏蔽了不同容器的差異性,以統一的方式對容器通路、周遊 ;
  • 】:remove(Object),

    removeAll(Collection<?>)

    retainAll(Collection<?>)

    和 clear() 四個方法的實作均直接調用了具體容器的疊代器(由 iterator() 方法傳回)的 remove 方法 ;
  • 】:contains(Object o) 和

    containsAll(Collection<?> c)

    兩個方法的實作均直接或間接調用了元素的 equals 方法 ;
  • 基本方法 】:size 方法實作與具體容器相關, isEmpty 方法的實作均直接調用了 size 方法 ;
  • 容器與數組轉換 】:分别提供與泛型 toArray 方法 和 原生 toArray 方法;

   從源代碼中我們可以知道,幾乎所有方法的實作都與疊代器相關,并且有以下特點:

  • 執行各種不同的添加和移除方法在 Collection 接口中都是可選操作,因為他們會改變容器的結構;
  • Collection 接口中的讀取方法都不是可選操作 ;

四、Optional operations(可選操作)

1、簡述

  

執行各種不同的添加和移除方法在 Collection 接口中都是可選操作, 這意味着實作類并不需要為這些方法提供功能定義。

   這是一種很不尋常的接口定義方式。正如你所看到的那樣,接口是面向對象設計中的契約,它聲明 “無論你選擇如何實作該接口,我保證你可以向該接口發送這些消息。”但可選操作違反這個非常基本的原則,它聲明調用某些方法将不會執行有意義的行為,相反,他們會抛出異常。

  

為什麼會将方法定義為可選呢?

因為這樣做可以防止在設計中出現接口爆炸的情形。容器類庫中的設計看起來總是為了描述每個主題的變體,而最終患上了令人困惑的接口過剩症。甚至這麼做仍不能捕獲接口的各種特例,因為總是有人會發明新的接口。“未獲支援的操作” 這種方式可以實作 Java 容器類庫的一個重要目标:容器應易學易用。未獲支援的操作是一種特例,可以延遲到需要時實作。但是,為了讓這種方式能夠工作:

  1. UnsupportedOperationException 必須是一種罕見事件。即,對于大多數類而言,所有操作都應該可以工作,隻有在特例(例如,通過Arrays.asList()所得到的容器)中才會有未獲支援的操作 。在 Java 容器類庫中确實如此,因為你在 99% 的時間裡使用的容器類,如 ArrayList、LinkedList、HashSet 和 HashMap,以及其他的具體實作,都支援所有操作。這種設計留下了一個“後門”,如果你想建立新的 Collection, 但是沒有為 Collection 接口中的所有方法都提供有意義的定義,那麼它仍舊适合現有類庫。
  2. 如果一個操作是未獲支援的,那麼在實作接口的時候可能就會導緻 UnsupportedOperationException 異常,而不是将産品程式交給客戶後才出現此異常,這種情況是有道理的。畢竟,他表示程式設計上有錯誤:使用了不正确的接口實作。

     值得注意的是,未獲支援操作隻有在運作時才能探測到,是以他們表示動态類型檢查。

2、示例

  

最常見的未獲支援的操作,都來源于背後由固定尺寸的資料結構支援的容器。

當你用 Arrays.asList() 将數組轉換為 List 時,就會得到這樣的容器。此外,你還可以通過使用 Collection 類中“不可修改”的方法,選擇建立任何會抛出會抛出 UnsupportedOperationException 的容器。

   請看下面的例子:

public class Unsupported {
    static void test(String msg, List<String> list) {
        System.out.println("--- " + msg + " ---");
        Collection<String> c = list;
        Collection<String> subList = list.subList(, );
        // Copy of the sublist:
        Collection<String> c2 = new ArrayList<String>(subList);
        try {
            c.retainAll(c2);
        } catch (Exception e) {
            System.out.println("retainAll(): " + e);
        }
        try {
            c.removeAll(c2);
        } catch (Exception e) {
            System.out.println("removeAll(): " + e);
        }
        try {
            c.clear();
        } catch (Exception e) {
            System.out.println("clear(): " + e);
        }
        try {
            c.add("X");
        } catch (Exception e) {
            System.out.println("add(): " + e);
        }
        try {
            c.addAll(c2);
        } catch (Exception e) {
            System.out.println("addAll(): " + e);
        }
        try {
            c.remove("C");
        } catch (Exception e) {
            System.out.println("remove(): " + e);
        }
        // The List.set() method modifies the value but
        // doesn’t change the size of the data structure:
        try {
            list.set(, "X");
        } catch (Exception e) {
            System.out.println("List.set(): " + e);
        }
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList("A B C D E F G H I J K L".split(" "));

        test("Modifiable Copy", new ArrayList<String>(list)); // 産生新的尺寸可調的
                                                                // ArrayList

        test("Arrays.asList()", list); // 産生固定尺寸的 ArrayList

        test("unmodifiableList()",
                Collections.unmodifiableList(new ArrayList<String>(list))); // 産生不可修改的清單
    }
} /* Output: 
--- Modifiable Copy --- 

--- Arrays.asList() --- 
retainAll(): java.lang.UnsupportedOperationException 
removeAll(): java.lang.UnsupportedOperationException 
clear(): java.lang.UnsupportedOperationException 
add(): java.lang.UnsupportedOperationException 
addAll(): java.lang.UnsupportedOperationException 
remove(): java.lang.UnsupportedOperationException 

--- unmodifiableList() --- 
retainAll(): java.lang.UnsupportedOperationException 
removeAll(): java.lang.UnsupportedOperationException 
clear(): java.lang.UnsupportedOperationException 
add(): java.lang.UnsupportedOperationException 
addAll(): java.lang.UnsupportedOperationException 
remove(): java.lang.UnsupportedOperationException 
List.set(): java.lang.UnsupportedOperationException 
           

  

因為 Arrays.asList() 實際上會産生一個 Arraylist ,它基于一個固定大小的數組,僅支援那些不會改變數組大小的操作。是以,任何會引起對底層資料結構的尺寸進行修改的方法(add/remove 相關)都會産生一個 UnsupportedOperationException 異常,以表示對該容器未獲支援操作的調用。

   是以,

Arrays.asList() 的真正意義在于:将其結果作為構造器參數傳遞給任何 Collection (或者使用 addAll 方法、Collections.addAll 靜态方法),這樣可以生成一個動态的容器

   由以上程式片段可知,

Arrays.asList() 傳回固定尺寸的List,而 Collections.unmodifiableList() 産生不可修改的清單。正如輸出所示,前者支援 set 操作,而後者不支援。若使用接口,那麼還需要兩個附加的接口,一個具有可以工作的 set 方法,另一個沒有,因為附加的接口對于 Collection 的各種不可修改子類型來說是必須的。是以,可選方法可以避免 接口爆炸

   針對 Arrays.asList() 這種情況給出解釋:

1.首先該方法的源碼為:

public static <T> List<T> asList(T... a) {
    return new ArrayList<T>(a);
    }
           

2.緊接着看上述方法所傳回的由固定尺寸的資料結構支援的容器源碼(部分):

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable
    {
    private static final long serialVersionUID = -L;
    private final E[] a;

    ArrayList(E[] array) {
            if (array==null)
                throw new NullPointerException();
        a = array;
    }

    public int size() {
        return a.length;
    }

    public Object[] toArray() {
        return a.clone();
    }

    public <T> T[] toArray(T[] a) {
        int size = size();
        if (a.length < size)
        return Arrays.copyOf(this.a, size,
                     (Class<? extends T[]>) a.getClass());
        System.arraycopy(this.a, , a, , size);
        if (a.length > size)
        a[size] = null;
        return a;
    }

    public E get(int index) {
        return a[index];
    }

    public E set(int index, E element) {        //該容器支援的操作
        E oldValue = a[index];
        a[index] = element;
        return oldValue;
    }

    public int indexOf(Object o) {
        if (o==null) {
            for (int i=; i<a.length; i++)
                if (a[i]==null)
                    return i;
                } else {
                    for (int i=; i<a.length; i++)
                        if (o.equals(a[i]))
                            return i;
                }
        return -;
    }

    public boolean contains(Object o) {
        return indexOf(o) != -;
    }
}

....
其餘代碼省略
....
           
針對 Add 操作:

該容器的該行為繼承于 AbstractList 抽象類,直接或間接調用 add(int index, E element) 方法,抛出 UnsupportedOperationException

// AbstractList 中的方法
public boolean add(E e) {
    add(size(), e);
    return true;
    }

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

public boolean addAll(int index, Collection<? extends E> c) {
    boolean modified = false;
    Iterator<? extends E> e = c.iterator();
    while (e.hasNext()) {
        add(index++, e.next());
        modified = true;
    }
    return modified;
}
           
針對 remove 操作:

該容器的 remove 相關行為間接繼承自 AbstractCollection 類, AbstractList 類并未完全重寫(隻重寫了 clear 操作)它們,如下:

//AbstractList 類中方法:
    //該方法繼承自 List 接口的:remove(int index) 方法,是 List 特有的方法
    public E remove(int index) {
    throw new UnsupportedOperationException();
    }


    //AbstractList 類重寫了 AbstractCollection 的 clear 方法
     public void clear() {
        removeRange(, size());
    }
   protected void removeRange(int fromIndex, int toIndex) {
        ListIterator<E> it = listIterator(fromIndex);
        for (int i=, n=toIndex-fromIndex; i<n; i++) {
            it.next();
            it.remove();           //直接調用了具體容器的疊代器(由 iterator() 方法傳回)的 remove 方法   
        }
    }
           

   AbstractList 類未重寫 remove(Object),

removeAll(Collection<?>)

retainAll(Collection<?>)

,這些方法實際上繼承自 AbstractCollection 類:

//AbstractCollection 類中方法:
    public boolean remove(Object o) {
    Iterator<E> e = iterator();
    if (o==null) {
        while (e.hasNext()) {
        if (e.next()==null) {
            e.remove();
            return true;
        }
        }
    } else {
        while (e.hasNext()) {
        if (o.equals(e.next())) {
            e.remove();           //直接調用了具體容器的疊代器(由 iterator() 方法傳回)的 remove 方法   
            return true;
        }
        }
    }
    return false;
    }

public boolean removeAll(Collection<?> c) {
    boolean modified = false;
    Iterator<?> e = iterator();
    while (e.hasNext()) {
        if (c.contains(e.next())) {
        e.remove();           //直接調用了具體容器的疊代器(由 iterator() 方法傳回)的 remove 方法   
        modified = true;
        }
    }
    return modified;
    }

 public boolean retainAll(Collection<?> c) {
    boolean modified = false;
    Iterator<E> e = iterator();
    while (e.hasNext()) {
        if (!c.contains(e.next())) {
        e.remove();            //直接調用了具體容器的疊代器(由 iterator() 方法傳回)的 remove 方法          
        modified = true;
        }
    }
    return modified;
    }
           

   由上面兩段代碼可知,remove(Object),

removeAll(Collection<?>)

retainAll(Collection<?>)

和 clear() 這四種操作均直接調用了具體容器的疊代器(由 iterator() 方法傳回)的 remove 方法 ,我們再看 AbstractList 類提供的 Iterator 中的 remove 方法:

private class Itr implements Iterator<E> {
    /**
     * Index of element to be returned by subsequent call to next.
     */
    int cursor = ;

    /**
     * Index of element returned by most recent call to next or
     * previous.  Reset to -1 if this element is deleted by a call
     * to remove.
     */
    int lastRet = -;

    /**
     * The modCount value that the iterator believes that the backing
     * List should have.  If this expectation is violated, the iterator
     * has detected concurrent modification.
     */
    int expectedModCount = modCount;

    public boolean hasNext() {
            return cursor != size();
    }

    public E next() {
            checkForComodification();
        try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
        } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet == -)
        throw new IllegalStateException();
            checkForComodification();

        try {
        AbstractList.this.remove(lastRet);            //直接調用 AbstractList 容器的 remove(int index) 方法,抛出 UnsupportedOperationException      
        if (lastRet < cursor)
            cursor--;
        lastRet = -;
        expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
    }
           

   綜上可知,示例中的 Arrays.asList() 部分為何會導緻那樣的結果。

引用:

JDK 1.6.0

《Java 程式設計思想(第4版)》