在學習java中的collection時注意到,collection層次的根接口Collection實現了Iterable<T>接口(位於java.lang包中),實現這個接口允許對象成為 "foreach" 語句的目標,而此接口中的唯一方法,實現的就是返回一個在一組 T 類型的元素上進行迭代的迭代器。
一、迭代器Iterator
接口:Iterator<T>
1 public interface Iterator<E>{ 2 3 boolean hasNext(); 4 5 E next(); 6 7 void remove(); 8 }
查看Iterator接口API可以知道,這是對collection進行迭代的迭代器。迭代器允許調用者利用定義良好的語義在迭代期間從迭代器所指向的 collection 移除元素。
尤其值得注意的是此迭代器remove()方法的使用:從迭代器指向的 collection 中移除迭代器返回的最后一個元素(可選操作)。每次調用 next 只能調用一次此方法。如果進行迭代時用調用此方法(remove方法)之外的其他方式修改了該迭代器所指向的 collection,則迭代器的行為是不確定的。 接口設計人員在設計Iterator<T>接口的時候已經指出,在進行迭代時如果調用了除了迭代器的remove()方法修改了該迭代器所指向的collection,則會造成不確定的后果。具體出現什么后果依迭代器的具體實現而定。針對這種不確定的后果可能出現的情況,在學習ArrayList時遇到了其中一種:迭代器拋出 ConcurrentModificationException異常
。具體異常情況如下代碼所示:
1 import java.util.ArrayList; 2 import java.util.Collection; 3 import java.util.Iterator; 4 5 public class ItaratorTest { 6 7 public static void main(String[] args) { 8 Collection<String> list = new ArrayList<String>(); 9 list.add("Android"); 10 list.add("IOS"); 11 list.add("Windows Mobile"); 12 13 Iterator<String> iterator = list.iterator(); 14 while (iterator.hasNext()) { 15 String lang = iterator.next(); 16 list.remove(lang);//will throw ConcurrentModificationException 17 } 18 } 19 20 }
此段代碼在運行時會拋出ConcurrentModificationException異常,因為我們在迭代器運行期間沒有用iterator的remove()方法來刪除元素,而是使用ArrayList的 remove()方法改變了迭代器所指向的collection。這就違反了迭代器的設計原則,所以發生了異常。
所報異常情況如下所示:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at Text.ItaratorTest.main(ItaratorTest.java:17)
二、for-each循環與迭代器Iterator<T>
從Java5起,在Java中有了for-each循環,可以用來循環遍歷collection和array。Foreach循環允許你在無需保持傳統for循環中的索引,或在使用iterator /ListIterator(ArrayList中的一種迭代器實現)時無需調用while循環中的hasNext()方法就能遍歷collection。for-each循環簡化了任何Collection或array的遍歷過程。但是使用foreach循環也有兩點需要注意。
- 使用foreach循環的對象,必須實現了Iterable<T>接口
請看如下示例:
1 import java.util.ArrayList; 2 3 public class ForeachTest1 { 4 5 public static void main(String args[]) { 6 CustomCollection<String> myCollection = new CustomCollection<String>(); 7 myCollection.add("Java"); 8 myCollection.add("Scala"); 9 myCollection.add("Groovy"); 10 11 // What does this code will do, print language, throw exception or 12 // compile time error 13 for (String language : myCollection) { 14 System.out.println(language); 15 } 16 } 17 18 private class CustomCollection<T> { 19 private ArrayList<T> bucket; 20 21 public CustomCollection() { 22 bucket = new ArrayList(); 23 } 24 25 public int size() { 26 return bucket.size(); 27 } 28 29 public boolean isEmpty() { 30 return bucket.isEmpty(); 31 } 32 33 public boolean contains(T o) { 34 return bucket.contains(o); 35 } 36 37 public boolean add(T e) { 38 return bucket.add(e); 39 } 40 41 public boolean remove(T o) { 42 return bucket.remove(o); 43 } 44 45 } 46 }
上述代碼將無法通過編譯,這是因為代碼中的CustomCollection類沒有實現Iterable<T>接口,編譯期的報錯如下:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Can only iterate over an array or an instance of java.lang.Iterable
at Text.ForeachTest1.main(ForeachTest1.java:15)
事實上,無需等到編譯時才發現報錯,eclipse會在這段代碼寫完之后就會在foreach循環處顯示錯誤:Can only iterate over an array or an instance of java.lang.Iterable
從上述示例可以再次得到確認的是,foreach循環只適用於實現了Iterable<T>接口的對象。由於所有內置Collection類都實現了java.util.Collection接口,已經繼承了Iterable,所以為了解決上述問題,可以選擇簡單地讓CustomCollection實現Collection接口或者繼承AbstractCollection。解決方式如下:
1 import java.util.AbstractCollection; 2 import java.util.ArrayList; 3 import java.util.Iterator; 4 5 public class ForeachTest { 6 public static void main(String args[]) { 7 CustomCollection<String> myCollection = new CustomCollection<String>(); 8 myCollection.add("Java"); 9 myCollection.add("Scala"); 10 myCollection.add("Groovy"); 11 for (String language : myCollection) { 12 System.out.println(language); 13 } 14 } 15 16 private static class CustomCollection<T> extends AbstractCollection<T> { 17 private ArrayList<T> bucket; 18 19 public CustomCollection() { 20 bucket = new ArrayList(); 21 } 22 23 public int size() { 24 return bucket.size(); 25 } 26 27 public boolean isEmpty() { 28 return bucket.isEmpty(); 29 } 30 31 public boolean contains(Object o) { 32 return bucket.contains(o); 33 } 34 35 public boolean add(T e) { 36 return bucket.add(e); 37 } 38 39 public boolean remove(Object o) { 40 return bucket.remove(o); 41 } 42 43 @Override 44 public Iterator<T> iterator() { 45 // TODO Auto-generated method stub 46 return bucket.iterator(); 47 } 48 } 49 }
2.foreach循環的內部實現也是依靠Iterator進行實現的
為了驗證foreach循環是使用Iterator作為內部實現這一事實,我們依然采用本文最開始的實例進行驗證:
1 public class ItaratorTest { 2 3 public static void main(String[] args) { 4 Collection<String> list = new ArrayList<String>(); 5 list.add("Android"); 6 list.add("IOS"); 7 list.add("Windows Mobile"); 8 9 // example1 10 // Iterator<String> iterator = list.iterator(); 11 // while (iterator.hasNext()) { 12 // String lang = iterator.next(); 13 // list.remove(lang); 14 // } 15 16 // example 2 17 for (String language : list) { 18 list.remove(language); 19 } 20 } 21 22 }
程序運行時所報異常:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at Text.ItaratorTest.main(ItaratorTest.java:22)
此異常正說明了for-each循環內部使用了Iterator來遍歷Collection,它也調用了Iterator.next(),這會檢查(元素的)變化並拋出ConcurrentModificationException。
總結:
- 在遍歷collection時,如果要在遍歷期間修改collection,則必須通過Iterator/listIterator來實現,否則可能會發生“不確定的后果”。
- foreach循環通過iterator實現,使用foreach循環的對象必須實現Iterable接口