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接口中定義一些通用的方法。這些方法都比較基本而且使用都比較頻繁,是以我們需要對每一個方法都記錄,按方法的作用我們可以分為以下幾類:
-
添加
共兩個方法,分别是add和addAll, 分别是接收一個對象和一個Collection對象。
-
删除
共四個方法,remove, removeAll, retainAll,clear, 其中需要說的是retainAll,這個操作接受一個Collection作為參數,取兩個集合的交集。
-
查找
共兩個方法, contains, containsAll, 判斷集合中是否有某個或某些元素
-
轉換
共三個方法, toArray(), toArray(T t[])和iterator, 前兩個是把集合轉化為數組,另外一個是轉化為一個Iterator對象,可用于周遊,這個方法其實在其父接口Iterable中也有定義。
-
求大小
共兩個方法, size()和isEmpty(), 分别是求長度和判斷集合是否為空。
-
比較
共兩個方法,equals和hashCode,這兩個方法是從object中繼承過來的。
以上共15個方法,大多數還是很好了解。需要重點關注的是轉換類的兩個方法,由于Collection繼承了Iterable,是以所有的collection都可以通過foreach的方式來調用,這是JDK1.5之後的一種文法糖。
關于Collection的介紹就到這裡,下面接着看一下其直接骨架類AbstractCollection.
AbstractCollection介紹
雖然Collection中的方法很多,其不同子類型的表現也不一樣,但事實上這15個方法中有很多都是跟具體的子類沒有關系的,為了簡化具體Collection類的設計, JDK提供了一個抽象類AbstractCollection,對其中的大多數方法進行了實作。
方法的實作沒有必要依次去介紹,這裡主要介紹這個子類的一些特點及幾個重要方法的實作。
- 本類預設是不是可修改的,即不支援add,由于addAll依賴于add,是以addAll也是不支援的,要支援添加功能,就需要重寫這個add方法
- size,iterator這兩個方法沒有實作,是以要編寫自己的collection,需要實作這兩個方法即可。
- 其它所有方法都有實作,不過涉及到周遊的都依賴于iterator()傳回的疊代器,删除也依賴于疊代器提供的删除方法。
- 本類沒有對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的時候,我們才能更好的了解。