一、要點
- 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> { ... }

2、行為
java.util.Collection 接口是描述 Set 和 List 集合類型的根接口,下面給出了Collection 的所有行為(不包括從Object 繼承而來的方法),是以它們也刻畫了 Set 或 List 執行的所有行為(List 還有額外的功能)。Collection 的行為分類和詳細清單如下 :
表1. Collection 的操作
Function | Introduction | Note |
---|---|---|
boolean add(T) | 将指定元素添加到集合中,若未将該元素添加到容器,傳回 false | 可選操作 |
boolean addAll( ) | 添加參數中的所有元素,隻要添加進任意元素則傳回 true | 可選操作 |
Iterator iterator( ) | 周遊容器中的元素(統一的通路、周遊方法,對外屏蔽了不同容器的差異性,疊代器模式) | 依賴于具體實作 |
boolean remove(Object) | 向容器移除指定元素,若移除動作發生,則傳回 true(若有重複對象,一次隻删除其中一個) | 可選操作 |
boolean removeAll( ) | 向容器移除參數中所有元素,若有移除動作發生,則傳回 true | 可選操作 |
Boolean retainAll( ) | 隻儲存參數中的元素,隻要Collection 發生改變,則傳回 true | 可選操作 |
void clear() | 移除容器中所有元素 | 可選操作 |
boolean contains(T) | 若容器持有該元素,傳回 true | 依賴于equals() |
Boolean containsAll( ) | 若容器持有參數中的所有元素,傳回 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 構造方法。
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<?>)
和 clear() 四個方法的實作均直接調用了具體容器的疊代器(由 iterator() 方法傳回)的 remove 方法 ;retainAll(Collection<?>)
- 【 查 】:contains(Object o) 和
兩個方法的實作均直接或間接調用了元素的 equals 方法 ;containsAll(Collection<?> c)
- 【 基本方法 】:size 方法實作與具體容器相關, isEmpty 方法的實作均直接調用了 size 方法 ;
- 【 容器與數組轉換 】:分别提供與泛型 toArray 方法 和 原生 toArray 方法;
從源代碼中我們可以知道,幾乎所有方法的實作都與疊代器相關,并且有以下特點:
- 執行各種不同的添加和移除方法在 Collection 接口中都是可選操作,因為他們會改變容器的結構;
- Collection 接口中的讀取方法都不是可選操作 ;
四、Optional operations(可選操作)
1、簡述
執行各種不同的添加和移除方法在 Collection 接口中都是可選操作, 這意味着實作類并不需要為這些方法提供功能定義。
這是一種很不尋常的接口定義方式。正如你所看到的那樣,接口是面向對象設計中的契約,它聲明 “無論你選擇如何實作該接口,我保證你可以向該接口發送這些消息。”但可選操作違反這個非常基本的原則,它聲明調用某些方法将不會執行有意義的行為,相反,他們會抛出異常。
為什麼會将方法定義為可選呢?
因為這樣做可以防止在設計中出現接口爆炸的情形。容器類庫中的設計看起來總是為了描述每個主題的變體,而最終患上了令人困惑的接口過剩症。甚至這麼做仍不能捕獲接口的各種特例,因為總是有人會發明新的接口。“未獲支援的操作” 這種方式可以實作 Java 容器類庫的一個重要目标:容器應易學易用。未獲支援的操作是一種特例,可以延遲到需要時實作。但是,為了讓這種方式能夠工作:
- UnsupportedOperationException 必須是一種罕見事件。即,對于大多數類而言,所有操作都應該可以工作,隻有在特例(例如,通過Arrays.asList()所得到的容器)中才會有未獲支援的操作 。在 Java 容器類庫中确實如此,因為你在 99% 的時間裡使用的容器類,如 ArrayList、LinkedList、HashSet 和 HashMap,以及其他的具體實作,都支援所有操作。這種設計留下了一個“後門”,如果你想建立新的 Collection, 但是沒有為 Collection 接口中的所有方法都提供有意義的定義,那麼它仍舊适合現有類庫。
-
如果一個操作是未獲支援的,那麼在實作接口的時候可能就會導緻 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版)》