第四十六條 for-each迴圈優先於傳統的for迴圈
在java 1.5 版本之前,我們對集合的遍歷,首選用迭代器
List c = new ArrayList<>();
c.add("a");
for(Iterator i = c.iterator(); i.hasNext(); ){
String s = (String) i.next();
System.out.println(s);
}
遍歷陣列,用for迴圈
for(int i = 0; i< a.length; i++){
System.out.println(a[i]);
}
經過上一條,防止 a.length 多次呼叫,可以優化,改為
for(int i = 0, count = a.length; i< count; i++){
System.out.println(a[i]);
}
上述用for比用while效果要好,但並不是很完美。迭代器和索引會造成某些混亂,或者一不留神,拷貝錯誤,把一些引數複製錯了,就會造成誤傷,幸運的是,增強for在java 1.5 時出現了,隱藏了迭代器和索引,適用於 集合 陣列 ,上述方法可修改為
List<String> c = new ArrayList<>();
for(String s : c){
System.out.println(s);
}
for(int i : a){
System.out.println(a[i]);
}
我們發現,for-each也就是增強for,裡面沒有迭代器與索引,只有對用的物件,這個可以理解是一個封裝,對索引和集合的個數一次計算清楚,我們直接呼叫即可。對於兩個集合的遍歷,按照數中的例子,把列舉裝到集合裡,每個集合對應的就是列舉的值
public enum Suit {
CLUB,DIAMOND,HEART,SPARE
}
public enum Rank {
ACE,DEUCE,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE,TEN,JACK,QUEEN,KING
}
void test1(){
Collection<Suit> suit = Arrays.asList(Suit.values());
Collection<Rank> rank = Arrays.asList(Rank.values());
for (Iterator<Suit> i = suit.iterator(); i.hasNext(); ) {
for (Iterator<Rank> j = rank.iterator(); j.hasNext(); ) {
System.out.println(i.next() + "+" + j.next());
}
}
}
列印結果,估計有點出人意料
CLUB+ACE
DIAMOND+DEUCE
HEART+THREE
SPARE+FOUR
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractList$Itr.next(AbstractList.java:364)
為什麼會這樣呢,我們一行行來解讀程式碼,有兩個列舉型別,Suit有四種類型,Rank有十三中型別,他們都取了values()值,轉換為集合,也就是說集合 suit 元素個數是4個,集合 rank 元素個數是13個,下面是兩個for迴圈巢狀,重點來了,看第一個for迴圈,集合 suit 獲取了迭代器,判斷是否有下一個元素,此時假設索引為a,索引a在第一個元素前面,i.hasNext() 判斷索引a如果往後移,後面是否有元素,此時,肯定是有的,所以進入裡面的for迴圈,同理,集合rank 也獲取了迭代器,假設它的索引為b,j.hasNext() 也判斷索引b如果後移,是否還有元素,此時也有,重點來了,看裡面的程式碼 System.out.println(i.next() + "+" + j.next()); 這是個列印日誌,但問題是 i.next() 和 j.next(), 也就是說列印元素的同時,索引a 和 索引b 都往後移動了一位,然後繼續是裡層的for迴圈,此時 j.hasNext() 仍滿足條件,應為 rank 有13個元素,這時候索引b走到第一個元素之後,第二個元素之前,然後再次執行System.out.println(i.next() + "+" + j.next()); 我們發現,列印了兩次,i.next() 執行了兩次,對應的索引a 往後移動了兩位,j.next() 對應的索引b 也往後移動了兩位,然後重複上面步驟。也就是說,沒列印一次,兩個迭代器的索引都往後移動了,由於 外層元素為4個,裡層元素13個,裡層大於外層,所以在第五次列印時,i.next() 已經陣列越界了,因為沒有值了,此時外層for迴圈只執行了一次,內層for迴圈執行了五次,第五次報錯,因為內層for迴圈判斷的條件是 j.hasNext() ,也就是索引b 沒有越界,沒有判斷索引a 的越界,所以報錯了。即使不報錯,我們是要遍歷所有的組合情況,這個邏輯寫法也不正確。可以修改為
void test2(){
Collection<Suit> suit = Arrays.asList(Suit.values());
Collection<Rank> rank = Arrays.asList(Rank.values());
for (Iterator<Suit> i = suit.iterator(); i.hasNext(); ) {
Suit suitValue = i.next();
for (Iterator<Rank> j = rank.iterator(); j.hasNext(); ) {
System.out.println(suitValue + "+" + j.next());
}
}
}
把 i.next(); 也就是索引a 的後移,放在內層for迴圈之前,這樣,每層for迴圈控制好自己的索引,就可以了。 但是,如果用了增強for,那麼就簡單多了
void test3(){
Collection<Suit> suit = Arrays.asList(Suit.values());
Collection<Rank> rank = Arrays.asList(Rank.values());
for (Suit su : suit) {
for (Rank ra : rank) {
System.out.println(su + "+" + ra);
}
}
}
簡單明瞭,並且結果正確。
增強for可以遍歷陣列和集合,也能遍歷實現Iterable介面的物件,我們編寫類時,實現了這個介面,就可以讓呼叫者使用增強for了,大多數情況,增強for比較好用,有幾種情況特殊,無法使用,比如遍歷集合,刪除指定的元素,增強for迴圈允許呼叫一側remove()方法,但必須馬上break或return跳出迴圈或終止迴圈,超過一個remove()就會報錯,所以刪除元素最好還是用傳統for迴圈;遍歷陣列,並替換某些元素,也需要用傳統for;並行遍歷多個集合,要控制索引變數,這時候就需要傳統for迴圈了。