iterator介面原始碼分析(ArrayList中的實現)
iterator是一個迭代器介面,它裡面主要有:
boolean hasNext();
E next();
這兩個方法,第一個方法表示迭代器含有更多元素則返回true;否則返回false;
第二個方法是返回迭代器的下一個元素;
其中還有兩個實現方法:
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); }
接下來我們來看看在不同集合類中是如何去實現迭代器的:
1.ArrayList中的實現方式:
public Iterator<E> iterator() {
return new Itr();
}
我們可以看到在這裡使用了內部類:
private class Itr implements Iterator<E> { int cursor; // 返回下一個元素的索引 int lastRet = -1; // 返回最後一個元素的索引,如果沒有就這樣 int expectedModCount = modCount; Itr() {}
接下來我們看一下hasNext():
public boolean hasNext() {
return cursor != size;
}
在這裡使用了cursor與size比較返回的boolean值;@SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }
這裡呼叫的方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
這個方法是什麼作用呢?就是Fail-Fast機制,就是expectedModCount預期修改的次數,modCount就是實際修改的次數;
在這迭代器中,首先定義了一個expectedModCount=modCount預設值是0;這樣通過上面的checkForComodification()方法就可以判斷list集合在迭代過程中是否被其他執行緒修改過,這裡的修改就是集合的增刪改查;因為這些操作都能引起modCount數值加1的操作,這樣就使得迭代失敗了。
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
這一段需要說一下:
第一:為什麼我們要建立一個elementData陣列,而不是直接ArrayList.this.elementData[lastRet = i]取值?
這裡是為了減少定址次數,如果不定義,那麼判斷的時候定址一次,取值的時候又要定址一次;
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
迭代器的移除方法,這裡先不說實現,先問一個問題,既然迭代器也有移除,list本身也有移除,那麼他們之間有什麼不同呢?
那我們接下來來看一下ArrayList下的remove方法:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
這裡有個rangeCheck(index)方法,它是幹什麼的?
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
我們能夠看到,它是當前集合座標大於等於集合size的時候丟擲下標越界異常;接著我們往下看,這裡有個我們熟悉的變數 modCount++;在迭代器中我們有與之判斷的變數:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
就是這個方法,如果使用ArrayList的remove方法移除的話那麼 modCount就會加1,這樣迭代器就會報錯,所以說在使用迭代器進行遍歷的時候是不能使用移除,新增等功能的;E oldValue = elementData(index);
是要被移除的元素;
int numMoved = size - index - 1;
這個是計算被移除的元素座標之後的長度,為什麼要減1呢?由於座標是從0開始的。
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
當移除的不是最後一個元素的時候,就進行陣列複製,這裡說一下詳細的引數含義:
elementData:是原陣列;
index+1:原陣列複製的起始位置;
elementData:目標陣列;
index;目的陣列放置的起始位置;
numMoved:複製的長度;
執行完之後將原陣列置位null;便於jvm回收;
接下來我們看下迭代器實現的移除方法,看看有什麼不一樣的。
首先不同的是ArrayList裡面的remove需要傳參:1)一種是傳指標2)一種是傳元素
然後我們看他們的內部實現:
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
可以看到,迭代器的remove首先呼叫了ArrayList的remove方法,也就是咱們上面說的那些,那這裡喲偶什麼不同呢?
1)這裡沒有進行modCount++;也不能這麼說,其實還是有的,那就是呼叫ArrayList的remove時,在它裡面進行的modCount++;
那既然這樣,那迭代的時候移除元素,不一樣會會使得checkForComodification();方法丟擲異常嗎?
那我們來看第二個不同:
2)它這裡處理的很巧妙,就是最後再重新給expectedModCount賦值,這樣就能一直保持一樣了,這一點ArrayList中的remove方法卻沒有這個;
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
然後我們還看到一個問題,那就是另兩個賦值是幹嘛用的?
這就要結合迭代器的next來看了,也就是說:只有當迭代器限制性next之後,才能remove,否則remove會跑出異常,why?
我們來看一下開頭的判斷:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
lastRet判斷小於0就要丟擲異常,而我們預設的值是-1;那麼好吧肯定得獲取到元素的索引吧,而且你也能看到在上面remove方法中lastRet又重新賦值了-1;這樣就可以看出來如果不去執行其他的操作,而直接使用remove是不可能的。
我們再來看一下next方法,它裡面有一句這樣的賦值:
return (E) elementData[lastRet = i];
i是什麼?就是cursor:下一個元素的索引,而在這裡i卻是當前的索引,因為在cursor+1之前,就已經賦值給i了,所以這裡是當前的索引,也就是0;
到這裡你是不是已經看出來上面的問題了,只有每次呼叫next方法獲取當前的索引,你才能夠將其移除。
還有一個forEachRemaining方法,這個就不說了,這是jdk1.8加入的一種Lamdba表示式。
到這裡我們可以來做幾個小例子來鞏固一下:
1)看一下下面的程式會不會執行結果是什麼?
public static void main(String[] args) {
ArrayList<String> a=new ArrayList<String>();
for(int j=0;j<5;j++){
a.add("a");
}
Iterator<String> iterator = a.iterator();
while(iterator.hasNext()){
iterator.remove();
}
System.out.println(a.size());
}
這裡執行後會跑出異常:就是這裡,我們沒有獲取當前的索引;
2)這個會怎麼樣
public static void main(String[] args) {
ArrayList<String> a=new ArrayList<String>();
for(int j=0;j<5;j++){
a.add("a");
}
for(int i=0;i<a.size();i++){
if("a".equals(a.get(i))){
a.remove(i);
}
}
System.out.println(a.size());
}
這個會正常執行的,但是結果卻是存在問題的,這是因為你每次遍歷的時候i都加了1,但是你移除之後,原來的元素的索引都產生了變化,即減了1,這樣下一次再移除的時候就會從索引1開始,這樣變成0的索引就無法刪除了,這裡考察了那個問題就是remove中的複製:
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
3)那增強for迴圈呢?
public static void main(String[] args) {
ArrayList<String> a=new ArrayList<String>();
for(int j=0;j<5;j++){
a.add("a");
}
for(String b:a){
if("a".equals(b)){
a.remove(b);
}
}
System.out.println(a.size());
}
這個一樣是跑出異常的,
首先說一下,增強for迴圈其實就是迭代器的next方法,這個大哥斷點你就可以看到,那麼好了我們知道了它呼叫了迭代器的next方法,此方法裡面有個 checkForComodification();校驗,第一次是成功的進入了,然後呼叫ArrayList的remove方法,modCount++了,而ArrayList中的remove有沒有最後 expectedModCount = modCount;進行賦值,因此會跑出異常:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
恩,這樣就可以了,那有沒有可以一邊迭代一邊修改的迭代器呢,有的,就是listIterator迭代器,看名字就知道它的作用是list集合,而不是適應所有的集合的。這個之後再說。