這篇文章會詳解上篇關於迭代器中出現的問題,當然說是詳解,其實我也只能在自己能力內對foreach,迭代器的機制進行了解。其中以arraylist為例子,包含了jdk的源代碼。
首先,for是大家都很熟悉的循環語法,它的基礎規則和使用為:
編程中用於循環處理的語句。Java的for語句形式有兩種:一種是和C語言中的for語句形式一樣,另一種形式用於在集合和數組之中進行迭代。有時候把這種形式稱為增強的for(enhanced for)語句,它可以使循環更加緊湊和容易閱讀。它的一般形式為: for(;;) 語句; 初始化總是一個賦值語句,它用來給循環控制變量賦初值;條件表達式是一個關系表達式,它決定什么時候退出循環;增量定義循環控制變量每循環一次后按什么方式變化。這三個部分之間用";"分開。
這一段是話百度的解釋,但事實上這個地方有一點不太准確的地方,foreach確實只能針對iterator進行迭代,但是與我在開頭所提到的迭代器遍歷是由一定區別的,我在后面會通過csdn上某位同志遇到的問題進行詳細解釋。所以在這個時候,我更願意將for,foreach和迭代器遍歷着三種方法分開,而不是將foreach歸為迭代器的一種遍歷方法。
三種方式的區別:
1)形式區別:
對於for循環,我們采用:
for(int i=0;i<arr.size();i++){...}
對於foreach:
for(int i:arr){...}
(當然這里的int只是舉例子,這里可以是所有實現iterator的接口對象);
在這里多說兩句,foreach只能針對實現了iterable接口的對象,其中具體怎么實現的,我有做了部分了解。我去查看了一個foreach的測試程序的字節碼。當然這不是今天的重點,我會重新開一個篇章講述關於java匯編中字節碼的事情。如果有興趣的同學可以點擊下面這個網站:
http://www.cnblogs.com/vinozly/p/5465454.html;
對於迭代器的形式:
Iterator it = arr.iterator(); while(it.hasNext()){ object o =it.next(); ...}
;
2)條件差別
for需要知道數組或者集合的大小,而且需要時有序的,不然無法遍歷;
foreach和iterator不需要知道數組或者集合的大小,他們都是得到集合內的每一個元素然后進行處理;
3)多態差別
for和foreach都需要知道自己的集合類型,甚至要知道自己集合內的元素類型,不能實現多態。這個使用的語法上都可以表示出來。
Iterator是一個接口累心,它不關心集合的累心和集合內的元素類型,因為它是通過hasnext和next來進行下一個元素的判斷和獲取,這一切都是在集合類型定義的時候就完成的事情。迭代器統一了對容器的訪問模式,這也是對接口解耦的最好表現。
4)用法差別
for一般可以用於簡單的順序集合,並且可以預測集合的大小;
foreach可以遍歷任何集合或者數組,但是使用者需要知道遍歷元素的類型。
iterator是最強大的,它可以隨之修改元素內部的元素。可以在遍歷的時刻用remove()!!!!我的意思是其他兩個都不可以!!!
而且iterator不需要知道元素類型和元素大小,通過hasnext()判斷是否遍歷完所有元素。
而且在對范型的集合進行遍歷的時候,iterator是不二的選擇,就是因為不需要知道元素類型便可以遍歷。
這里討論完了三個具體的區別,但是我接觸到了一個新的東西叫接口解耦。好吧,我會再寫一個文章講一下接口解耦是什么。(感覺坑越填愈多)
接下來講一下iterator實現遍歷的具體機制:
我們首先從一個問題程序入手,這個程序如下:
1 package iteratorTest; 2 import java.util.*; 3 4 public class TestIterator { 5 public static void main(String[] args){ 6 List<String> list=new ArrayList<String>(); 7 8 list.add("1"); 9 list.add("2"); 10 for(String temp : list){ 11 if("2".equals(temp)) 12 list.remove(temp); 13 } 14 15 for(String temp : list){ 16 System.out.println(temp); 17 } 18 19 Iterator<String> it=list.iterator(); 20 /*while(it.hasNext()){ 21 String temp=it.next(); 22 if("2".equals(temp)) 23 it.remove(); 24 } 25 26 for(String temp:list){ 27 System.out.println(temp); 28 }*/ 29 } 30 }
我們匯編的時候,程序會出現下面的錯誤:

當然,我如果更改程序把“1”刪除,這個時候就沒有問題。我當時也是一臉懵逼呀。
從錯誤中可以看到,是checForComodification出現了報錯。。那我們現在就去看這個地方的源代碼:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
這里設計一個java關於多線程的知識,因為iterator是禁止多線程操作的(當然vector的使用會在在后面寫。- -!真的是坑越填越多),java用了一個非常簡單的機制,就是modcount。記錄對集合進行改變的數量,創建一個迭代器與當前集合是要緊耦合的。我不能再我遍歷的時候同時去改變這個集合元素,不然會造成混亂。
好,這里我們可以確定是modcount不等於expectedmodecount造成的錯誤,那么及時在foreach內調用next()方法的時候出現了這個問題。我們深入去查看.next()方法。在看之前,我想先把JDK文檔中arraylist的方法和數據結構給出來,這在后面理解的時候是非常重要的:

注意我所標記的fastremove方法上面的remove()方法和私有類Itr里面的remove()方法。很明顯Itr是一個iterator接口類。注意了!!!但是在foreach的循環語句里面,我們使用的是上面的remove方法,因為我們並沒有構造list的迭代器。但是報道的錯誤信息來源於這個私有類里面的next是foreach擅自為我們進行構造的迭代器,而我們沒有使用這個迭代器里面的remove()!!!!!在研究源碼的時候我真的是載在這里好久好久。
我們使用的list.remove()是使用的arraylist自身的remove()方法,而不是構造器的方法。在使用的時候我還是清醒的,在調查源代碼的過程中整個人都懵逼了。我給出兩個remove的具體代碼,大家就會知道,arraylist自身方法的remove是不會去調整exceptedmodcount的值,但是卻增加了modcount,所以在foreach語句中使用next的checkmodcount的時候發生了錯誤。
arraylist.remove():
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; 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 }
Itr私有迭代器的remove():
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(); } }
很明顯迭代器中的remove有意識的改變expectedmodcount來使其不報錯。而foreach使用的arraylist.remove,改變了modcount,所以才會出現報錯現象。
然而刪除“1”不會報錯,是因為刪除過后foreach會調用迭代器中的hasnext()進行判斷,代碼如下:
public boolean hasNext() { return cursor != size; }
這個時候沒有下一個元素,所以不會在arraylist.remove()后調用Itr.next()方法,所以不會進行判斷。
這里給出Itr.next()的代碼:
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]; }
可以看到,在Itr.next()的一開始便檢查modcount於expectedmodcount是否相同。
通過上面的了解,我們使用iterator為什么能夠在遍歷的時候進行刪除操作也可以理解了:
在Itr.remove()方法內有意的將expectedmodcount賦值為modcount,所以不會產生checkformodcount的錯誤。
ps:真的,我在這里研究這么久是因為我看了一個評論,它就是將foreach的機制和arraylist.remove, Itr.remove弄混了,一直在分析迭代器內部是如何影響modcount和expectedcount的。還得到了很多很多贊。我在理解源碼的過程中表示,這不是吧expectedmodcount也改變了的么。
所以有時候,大家最好能通過源碼了解的,都自己去看源碼吧,大家對問題有不同的看法,也會有錯誤,但是源碼是不變的。
這篇文章寫到這里,很多問題也沒有深入,關於foreach的實現和字節碼,有時間就會做的。
我還會另開一個數據庫的篇章,以懷戀我drop掉的數據庫課。不是我不想上,是印度教授真的弄的我頭疼。
最后,我想說的就是:
開源大法好!!!!
