From Gossip@caterpillar

Java Essence: 神奇的 foreach



 


如果你要走訪陣列中所有元素,通常你會這麼寫:
public void go(int[] arr) {
    for(int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}

事實上很多時候,從頭到尾走訪一個陣列(或Collection)中所有元素,是很常見的需求,上面的寫法並沒有錯,只不過索引資訊基本上是不需要的,硬要寫是很冗長的事。

在J2SE 5.0之後,引進了foreach新語法,上例你可以這麼改寫:
public void go(int[] arr) {
    for(int element : arr) {
        System.out.println(element);
    }
}

這個語法其實不僅限於陣列,還可以用在List、Set等。例如若是List實作物件,可以這麼寫:
public void go(List<String> list) {
    for(String element : list) {
        System.out.println(element);
    }
}

如果是Set也是可以的,例如:
public void go(Set<String> set) {
    for(String element : set) {
        System.out.println(element);
    }
}

事實上,只要是Collection介面的實作物件,都可以用foreach語法:
public void go(Collection<String> collection) {
    for(String element : collection) {
        System.out.println(element);
    }
}

Java的foreach語法,其實是編譯器給的語法蜜糖。如果foreach要走訪的是陣列,事實上,編譯器會自動展開為以下的程式碼:
public void go(int ai[]) {
    for(int i = 0; i < ai.length; i++)
        System.out.println(ai[i]);
}

若foreach運用在Collection的實作物件上,其實編譯器會展開為:
public void go(Collection collection) {
    String s;
    for(Iterator iterator = collection.iterator();
        iterator.hasNext(); System.out.println(s))

        s = (String)iterator.next();
}

無論是Collection、List或Set,展開後皆利用iterator()方法傳回Iterator物件,並利用Iterator來移動、傳回下一個物件,這是
Iterator 模式 的實現。

在J2SE 1.4之前,Collection介面上直接定義了iterator()方法必須實作,因此所有Collection實作物件,皆需實作iterator ()。在J2SE 5.0之後,引進了Iterable介面,在介面中定義了iterator()方法,該方法必須傳回Iterator物件,而Collection介面繼 承了Iterable介面,
因此所有Collection實作物件,皆需實作iterator(),因此所有Collection實作物件,都可以搭配使用foreach語法。

然而並非只有Collection介面的實作物件,才可以搭配foreach語法,事實上,由於Iterable介面的引進,使得只要是實作Iterable介面的物件,皆可搭配foreach語法來使用。例如:
  • Some.java
import java.util.*;
public class Some implements Iterable<String>{
private String[] strs;
private int index;
public Some(int length) {
strs = new String[length];
}
public void add(String element) {
strs[index++] = element;
}
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index;
public boolean hasNext() {
return index < strs.length;
}
public String next() {
return strs[index++];
}
public void remove() {
strs[index] = null;
}
};
}
}

Some物件實作了Iterable介面,因此你可以像以下這麼搭配foreach:
  • Main.java
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
Some some = new Some(5);
some.add("one");
some.add("two");
some.add("three");
some.add("four");
some.add("five");
for(String str : some) {
System.out.println(str);
}
}
}

若你反組譯程式,會發現編譯器作了這些事:
String s;
for(Iterator iterator = some.iterator();
    iterator.hasNext(); System.out.println(s))

    s = (String)iterator.next();