Java中的for迴圈用了那麼多次,但你真的瞭解它麼?

其實我們寫程式碼的時候一直都在使用for迴圈,但是偶爾還是會糾結用哪一個迴圈。

一、基礎的for迴圈

0、使用while也是一種迴圈方式,此處探究for相關的迴圈,就不做拓展了。

1、遍歷陣列的時候,初學時是使用的如下樣式的for迴圈:

for(int i=0;i

System。out。println(n);

}

2、而遍歷集合的時候使用的都是Iterator迭代器:

給定一組人名,兩兩組隊(此處允許自己和自己組隊),實現如下:

enum Option

想象中的寫法是:

Collection options = Arrays。asList(Option。values());

for(Iterator i = options。iterator(); i。hasNext();){

for (Iterator j = options。iterator(); j。hasNext();) {

System。out。println(i。next()+“ ”+j。next());

}

但是執行過後你會發現這段程式碼是有瑕疵的,出現的結果只有四組:

Java中的for迴圈用了那麼多次,但你真的瞭解它麼?

那麼剩下的組合去哪裡了呢?

這裡程式並不會丟擲異常,只是單純的因為i。next()每次都會取下一個值,所以就出現了上圖的情況。

但是,如果外部集合的元素大於內部元素:

例如下面這段程式碼:

enum OptionFirst

enum OptionSecond

public static void main(String[] args) {

Collection optionFirstCollection = Arrays。asList(OptionFirst。values());

Collection optionSecondCollection = Arrays。asList(OptionSecond。values());

for (Iterator i = optionFirstCollection。iterator(); i。hasNext(); ) {

for (Iterator j = optionSecondCollection。iterator(); j。hasNext(); ) {

System。out。println(i。next() + “ ” + j。next());

}

}

}

執行後,就會丟擲java。util。NoSuchElementException異常,造成的原因就是因為外部迴圈呼叫了多次,而內不迴圈因為元素不足,導致迴圈丟擲了這樣的異常。

要想解決這種困擾只需要在二次迴圈前新增一個變數來儲存外部元素;即可實現想要達到的效果。

Collection options = Arrays。asList(Option。values());

for(Iterator i = options。iterator(); i。hasNext();){

Option option = i。next();

for (Iterator j = options。iterator(); j。hasNext();) {

System。out。println(option+“ ”+j。next());

}

}

Java中的for迴圈用了那麼多次,但你真的瞭解它麼?

二、for-each迴圈

這種迴圈方式不論是陣列還是集合都實用,而且效率更高;表達形式更加簡潔明瞭。

for(Element e:elements){

System。out。println(e);

}

當再次遇到上面的兩兩組隊問題時,根本不需要考慮元素不足的問題,而且程式碼也簡潔多了:

for (Option option : options) {

for (Option rank : options) {

System。out。println(option + “ ” + rank);

}

}

《Effective Java》中是這樣子寫for-each迴圈的:

Java中的for迴圈用了那麼多次,但你真的瞭解它麼?

三、for-each is not god

說了for-each那麼多好處,但是它也不是神,並非萬能的,有那麼三種情況是它需要注意的。

3。1、解構過濾的時候不能使用

如果需要遍歷集合,並刪除選定的元素,就需要使用顯式的迭代器,以便可以呼叫它的remove方法。不過在Java8中增加的Collection的removeIf方法常常可以避免顯式的遍歷。

例如下面這段程式碼:

List list = new ArrayList();

list。add(“1”);

list。add(“2”);

for (String item : list) {

if (“1”。equals(item)) {

list。remove(item);

System。out。println(“執行if語句成功”);

}

}

直接執行這段程式碼是沒什麼問題的,陣列list能成功刪除元素1,也能列印對應語句。

但是,我們進行如下任意一種操作:

若把list。remove(item)換成list。add(“3”);操作如何?若在第6行新增list。add(“3”);那麼程式碼會出錯嗎?若把if語句中的“1”換成“2”,結果你感到意外嗎?

如果都能正確執行當然就不需要問了,所以3個都會報ConcurrentModificationException的異常;

Java中的for迴圈用了那麼多次,但你真的瞭解它麼?

執行結果異常

而出現這些情況的原因稍稍解釋下就是:

首先,這涉及多執行緒操作,Iterator是不支援多執行緒操作的,List類會在內部維護一個modCount的變數,用來記錄修改次數。

舉例:ArrayList原始碼

protected transient int modCount = 0;

每生成一個Iterator,Iterator就會記錄該modCount,每次呼叫next()方法就會將該記錄與外部類List的modCount進行對比,發現不相等就會丟擲多執行緒編輯異常。

為什麼這麼做呢?我的理解是你建立了一個迭代器,該迭代器和要遍歷的集合的內容是緊耦合的,意思就是這個迭代器對應的集合內容就是當前的內容,我肯定不會希望在我氣泡排序的時候,還有執行緒在向我的集合裡插入資料對吧?所以Java用了這種簡單的處理機制來禁止遍歷時修改集合。

至於為什麼刪除“1”就可以呢,原因在於foreach和迭代器的hasNext()方法,foreach這個語法,實際上就是

while(itr。hasNext()){

itr。next()

}

所以每次迴圈都會先執行hasNext(),那麼看看ArrayList的hasNext()是怎麼寫的:

public boolean hasNext() {

return cursor != size;

}

cursor是用於標記迭代器位置的變數,該變數由0開始,每次呼叫next執行+1操作,於是:

所以程式碼在執行刪除“1”後,size=1,cursor=1,此時hasNext()返回false,結束迴圈,因此你的迭代器並沒有呼叫next查詢第二個元素,也就無從檢測modCount了,因此也不會出現多執行緒修改異常;但當你刪除“2”時,迭代器呼叫了兩次next,此時size=1,cursor=2,hasNext()返回true,於是迭代器傻乎乎的就又去呼叫了一次next(),因此也引發了modCount不相等,丟擲多執行緒修改的異常。

當你的集合有三個元素的時候,你就會神奇的發現,刪除“1”是會丟擲異常的,但刪除“2”就沒有問題了,究其原因,和上面的程式執行順序是一致的。

因此,在《阿里巴巴Java開發手冊中有這樣一條規定》:

Java中的for迴圈用了那麼多次,但你真的瞭解它麼?

3。2、轉換

如果需要遍歷列表或陣列,並取代它的部分或者全部元素值,就需要使用列表迭代器或者陣列索引,以便替換元素的值。

因為for-each是一循到底的,中間不做停留和位置資訊的顯示;所以要替換元素就不能使用它了。

3。3、平行迭代

如果需要並行的遍歷多個集合,就需要顯式的控制迭代器或者索引變數,以便所有迭代器或者索引變數都可以同步前進(就像上面講述Iterator迭代器的時候提到的組合減少的情況,只想出現下標一一對應的元素組合)。

四、總結

for-each迴圈不僅適用於遍歷集合和陣列,而且能讓你遍歷任何實現Iterator介面的物件;最最關鍵的是它還沒有效能損失。而對陣列或集合進行修改(新增刪除操作),就要用for迴圈。

所以迴圈遍歷所有資料的時候,能用它的時候還是選擇它吧,嘻嘻。

TAG: 迭代hasNextNEXTIterator迴圈