iterable和for-each迴圈集合的注意事項
Java中,for-each迴圈簡化了任何Collection或array的遍歷過程,但並不是每個Java程式設計師都瞭解本文將要描述的for-each 迴圈的一些細節。與 Java5 釋出的其他術語:釋放別名泛型,自動封裝和可變引數不同,Java開發者對for-each迴圈的使用比任何其他特性更加頻繁,但當問及高階的for-each迴圈怎樣工作,或什麼是在for-each迴圈中使用Collection時的基本需求時,就不是每個人都能夠回答的了。
本篇教程和例子旨在通過深入研究for-each 迴圈中幾個有趣的難題來填補上述空白(說明上述問題)。好了,不再贅述,一起看看我們在Java5 for-each迴圈的第一個問題。
高階迴圈問題 1
考慮下面這段遍歷一個使用者自定義的aggregator或collection類的程式碼,這段程式碼將會打印出什麼,丟擲異常還是編譯器錯誤:
複製程式碼 程式碼如下:package test;
/**
* Java Class to show how for-each loop works in Java
*/
public class ForEachTest {
public static void main(String args[]){
CustomCollection<String> myCollection = new CustomCollection<String>();
myCollection.add("Java");
myCollection.add("Scala");
myCollection.add("Groovy");
//What does this code will do, print language, throw exception or compile time error
for(String language: myCollection){
System.out.println(language);
}
}
}
下面是我們的CustomCollection類,這是個引數為泛型的類,與任何其他的Collection類相似,依靠於ArrayList並提供從Collection中新增和刪除項的方法。
package test;
public class CustomCollection<T>{
private ArrayList<T> bucket;
public CustomCollection(){
bucket = new ArrayList();
}
public int size() {
return bucket.size();
}
public boolean isEmpty() {
return bucket.isEmpty();
}
public boolean contains(T o) {
return bucket.contains(o);
}
public boolean add(T e) {
return bucket.add(e);
}
public boolean remove(T o) {
return bucket.remove(o);
}
}
答案:
上述程式碼將無法通過編譯,這是因為我們的CustomCollection類沒有實現java.lang.Iterable介面,編譯期錯誤如下:
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - for-each not applicable to expression type
required: array or java.lang.Iterable
found: test.CustomCollection
at test.ForEachTest.main(ForEachTest.java:24)
從中瞭解到的一個有趣的事實是:for-each迴圈僅應用於實現了Iterable介面的Java array和Collection類,而且既然所有內建Collection類都實現了java.util.Collection介面,已經繼承了Iterable,這一細節通常會被忽略,這點可以在Collection介面的型別宣告“ public interface Collection extends Iterable”中看到。所以為了解決上述問題,你可以選擇簡單地讓CustomCollection實現Collection介面或者繼承AbstractCollection,這是預設的通用實現並展示瞭如何同時使用抽象類和介面以獲取更好的靈活性。現在讓我們來看看for-each迴圈的第二個難題:
Java for-each迴圈的第二個難題:
在下面的程式碼示例將會丟擲ConcurrentModificationException異常。這裡我們使用標準iterator和for-each迴圈遍歷ArrayList,隨後刪除元素,你需要找出哪段程式碼將會丟擲ConcurrentModificationException ,為什麼?請注意,答案可能是兩個都會,都不會或其中之一。
複製程式碼 程式碼如下:package test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* Java class to demonstrate inner working of for-each loop in Java
* @author Javin Paul
**/
public class ForEachTest2 {
public static void main(String args[]){
Collection<String> list = new ArrayList<String>();
list.add("Android");
list.add("iPhone");
list.add("Windows Mobile");
// Which Code will throw ConcurrentModificationException, both,
// none or one of them
// example 1
Iterator<String> itr = list.iterator();
while(itr.hasNext()){
String lang = itr.next();
list.remove(lang);
}
// example 2
for(String language: list){
list.remove(language);
}
}
}
大約70%的Java開發者都會說第一個程式碼塊會丟擲ConcurrentModificationException異常,因為我們沒有用iterator的remove方法來刪除元素,而是使用ArrayList的 remove()方法。但是,沒有多少Java開發者會說出for-each迴圈也會出現同樣的問題,因為我們在這裡沒有使用iterator。事實上,第二個程式碼片段也會丟擲ConcurrentModificationException異常,這點在解決了第一個困惑之後就變得很明顯了。既然for-each迴圈內部使用了Iterator來遍歷Collection,它也呼叫了Iterator.next(),這會檢查(元素的)變化並丟擲ConcurrentModificationException。你可以從下面的輸出中瞭解到這點,在註釋掉第一個程式碼段後,當你執行第二個程式碼段時會得到下面的輸出。
複製程式碼 程式碼如下:Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at test.ForEachTest2.main(ForEachTest2.java:34)
以上就是關於Java5 for-each迴圈的全部內容。我們已經看到了Java程式設計師在編寫遍歷Collection類的程式碼時產生的很多問題,特別是在遍歷collection的同時刪除元素的時候。請牢記,在從任何Collection(例如Map、Set或List)中刪除物件時總要使用Iterator的remove方法,也請謹記for-each迴圈只是標準Iterator程式碼標準用法之上的一種語法糖(syntactic sugar)而已。
譯者注:語法糖(syntactic sugar),也譯為糖衣語法,是由英國電腦科學家彼得·約翰·蘭達-Peter J. Landin發明的一個術語,指計算機語言中新增的某種語法,這種語法對語言的功能並沒有影響,但是更方便程式設計師使用。通常來說使用語法糖能夠增加程式的可讀性,從而減少程式程式碼出錯的機會。