「譯」Java集合框架系列教程三:Collection介面

迷渡發表於2013-01-22

一個集合表示一組物件。Collection介面被用來傳遞物件的集合,具有最強的通用性。例如,預設所有的集合實現都有一個構造器帶有一個Collection型別引數。這個構造器被稱作轉換構造器,用指定集合的元素來初始化新集合,而不管指定集合實現的是哪個集合介面和實現型別。換句話說,這個構造器使得我們可以轉化集合的型別(比如List到Set,Set到List)。

設想一下,假如你有一個Collection<String> c,它可能是一個List,也可能是一個Set,或者其他型別的Collection。按照上面的方法可以這樣初始化一個ArrayList包含c中所有元素:

List<String> list = new ArrayList<String>();

下面是Collection介面實現:

public interface Collection<E> extends Iterable<E> {
    // Basic operations
    int size();
    boolean isEmpty();
    boolean contains(Object element);
    // optional
    boolean add(E element);
    // optional
    boolean remove(Object element);
    Iterator<E> iterator();

    // Bulk operations
    boolean containsAll(Collection<?> c);
    // optional
    boolean addAll(Collection<? extends E> c); 
    // optional
    boolean removeAll(Collection<?> c);
    // optional
    boolean retainAll(Collection<?> c);
    // optional
    void clear();

    // Array operations
    Object[] toArray();
    <T> T[] toArray(T[] a);
}

這個介面做了我們期望一組物件能做的事情。這個介面通過size、isEmpty方法來告訴我們集合中還有多少元素。通過contains方法來判斷給定元素是否在集合中。通過add、remove方法來新增或移除元素。並且提供iterator方法來迭代訪問集合。

add方法足夠泛化,使得它既支援允許重複元素的集合,又支援不允許重複元素的集合。它保證集合在呼叫add方法後將包含指定元素,並且返回true,如果集合在呼叫add之後改變了。類似地,remove方法用來從集合中移除指定元素,前提是集合包含這個元素,並且返回true如果呼叫remove方法之後集合改變了。

遍歷集合

有兩種方法遍歷集合:

  • for-each
  • 迭代器訪問

fo-each:

for-each訪問如下:

for (Object o : collection)
   System.out.println(o);

迭代器訪問:

一個迭代器使得我們可以遍歷集合並且可以有選擇性地移除集合中的某些元素。我們可以通過呼叫集合的iterator方法來獲得集合的迭代器。下面是Iterator介面:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove(); //optional
}

hasNext方法指示集合中是否還有元素,next返回迭代中的下一個元素。remove方法移除next返回的元素。每呼叫一次next,最多隻能呼叫一次remove,否則會丟擲異常。比如下面程式碼遍歷集合a,並且刪除它的所有元素:

List<String> a = new ArrayList<String>();
a.add("hello");
a.add("world");
Iterator<String> iter = a.iterator();
while(iter.hasNext()) {
   System.out.print(iter.next() + "\t");
   iter.remove();
 }

上面如果把iter.remove放到iter.next()之前,就會丟擲java.lang.IllegalStateException異常。

注意Iterator.remove是遍歷集合期間修改集合的唯一安全方法。如果在遍歷集合期間用其他方法修改集合,會發生難以預料的事情。

使用迭代器而非for-each當你需要做下面的事情時: - 移除當前元素。for-each不是用來過濾集合中元素的好方法。應該用iterator來過濾集合中的元素,程式碼如下:

static void filter(Collection<?> c) {
    for (Iterator<?> it = c.iterator(); it.hasNext(); )
        if (!condition(it.next()))
            it.remove();
}

這段簡單的程式碼體現了多型,這意味著它可以支援所有的集合,而不管它的具體實現如何。這個例子也證明了利用Java集合框架編寫多型演算法是多麼的簡單。

集合介面批量操作

批量操作對整個集合進行某種操作。我們可以採用其他的基本操作實現對整個集合的操作,雖然那樣做可能不夠高效。下面是集合介面支援的批量操作:

  • containsAll — 返回true,如果集合包含指定集合中的所有元素
  • addAll — 將指定集合中的所有元素新增到目標集合中
  • removeAll — 從目標集合中移除所有也在指定集合中存在的元素,即從目標集合中移除交集元素
  • retainAll — 只保留目標集合和指定集合都含有的元素,即交集元素。如果執行retainALl操作後,目標集合被修改,則返回true。那麼什麼時候返回false呢?當目標集合含有的元素與指定集合完全一樣時。
  • clear - 清空目標集合中的所有元素。

上面幾個方法返回true,當目標集合在執行這些方法之後被修改了。

舉一個簡單的例子來證明批量操作的威力,考慮下面的程式碼,它用來從集合中移除指定元素e:

c.removeAll(Collections.singleton(e));

再舉一個更加特殊的例子,如果你想移除集合中的所有null元素:

c.removeAll(Collections.singleton(null));

這裡使用了Collections.singleton,這是一個靜態工廠方法,返回結果是一個只包含指定元素的不變Set。

集合介面的陣列操作

toArray方法被用來溝通collections和舊API(期望一個陣列作為輸入)。陣列操作使得集合被轉換為陣列。不帶任何引數的toArray方法建立一個物件的新陣列。更復雜的形式允許指定一個陣列引數或者為返回結果選擇一個執行時型別。

例如,設想c是一個Collection。下面的程式碼將c的內容填充到一個新開闢的物件陣列中,這個陣列的長度等於c的元素數目:

Object[] a = c.toArray();

又假設c只包含字串,即Collection<String> c。那麼下面的程式碼將返回一個字串陣列,陣列的長度還是等於c的元素數目:

String[] a = c.toArray(new String[0]);

相關文章