首頁>技術>

本文基於《Effective java》第58條

在此基礎之上加入了自己的理解。

for迴圈是平時寫程式碼用的最多的,但是之前看《Effective java》,大佬在某些場景寫並不推薦。結合著自己之前刷演算法題的經歷,受益匪淺。

一、for迴圈的缺點

在以往遍歷元素的時候,我們通常採用以下的形式:

問題1:迭代器或索引多次出現,容易造成使用錯誤

從上面兩種遍歷的程式碼上來看,對於陣列元素是透過索引i來遍歷的,但是整個for迴圈出現了四次i,對於物件元素是透過迭代器it來遍歷的,但是整個for迴圈出現了三次it。在for迴圈遍歷元素的時候,就有多次機會使用了錯誤的變數。而且有時候這些錯誤編譯器無法發現。對整個應用系統造成無法預知的錯誤。

問題2:遍歷物件元素時,需要注意容器型別

比如我們這裡使用的是list,當然還有可能是其他容器型別,這些型別在更改時比較麻煩。

問題3:巢狀迭代丟擲異常

這種情況比較複雜一些,先來搞個例子。比如說,我們想要列舉每種花,這些花有兩種屬性一種是顏色,一種是大小。

public class Main { //列舉顏色和尺寸 enum Color { RED, GREEN, BLUE, BLACK } enum Size { ONE, TWO, THREE, FOUR, FIVE,    SIX, SEVEN, EIGHT,NINE, TEN} //定義花 static class Flower{  public Flower(Color color, Size size) {} } public static void main(String[] args) {  Collection<Color> colors = Arrays.asList(Color.values());  Collection<Size> sizes = Arrays.asList(Size.values());  List<Flower> flowers = new ArrayList<>();  //for迴圈新增所有的花和尺寸  for (Iterator<Color> color = colors.iterator(); color.hasNext(); ) {   for (Iterator<Size> size = sizes.iterator(); size.hasNext(); ) {    flowers.add(new Flower(color.next(), size.next()));   }   }  }}

看似人畜無害,現在我們執行一波。

Exception in thread "main" java.util.NoSuchElementException at java.util.AbstractList$Itr.next(Unknown Source) at com.f2.Main.main(Main.java:25)

是不是感覺有點奇怪,好像雙重迴圈遍歷沒啥問題,但是出現了異常,原因是外部的Color迭代器呼叫了多次,第一層for迴圈被呼叫了,但是又在第二層for迴圈內部被呼叫了,所以color的next被呼叫完了。所以出現了NoSuchElementException。但是有時候也不會出現這種情況,場景是外部迴圈迭代器呼叫的次數剛好是內部呼叫的n倍。

問題4:巢狀迭代不是異常,但是結果不正確

這種情況是外部迴圈迭代器呼叫的次數剛好是內部呼叫的n倍。我們再來個例子:

public class Main { //列舉顏色 enum Color { RED, GREEN, BLUE, BLACK }  public static void main(String[] args) {  Collection<Color> colors = Arrays.asList(Color.values());  //兩層for迴圈  for (Iterator<Color> c1 = colors.iterator(); c1.hasNext(); ) {   for (Iterator<Color> c2 = colors.iterator(); c2.hasNext(); ) {    System.out.println(c1.next()+" "+c2.next());   }   }  }}

現在對顏色進行for迴圈遍歷,一共兩層for迴圈,因為一共有四種顏色,兩層for迴圈應該是列印16個結果。現在執行一遍看看結果:

RED REDGREEN GREENBLUE BLUEBLACK BLACK

沒錯,確實是列印了四條。原因和問題三是一樣的。有一種方式可以很好地解決這種巢狀的問題。

巢狀迭代問題解決:

直接看程式碼。既然是外部的迭代器it在內部使用了,那我在內部和外部之間用一個變數快取起來不就好了。

public class Main { //列舉顏色 enum Color { RED, GREEN, BLUE, BLACK } public static void main(String[] args) {  Collection<Color> colors = Arrays.asList(Color.values());  //for迴圈  for (Iterator<Color> c1 = colors.iterator(); c1.hasNext(); ) {   //用一個變數快取起來   Color c = c1.next();   for (Iterator<Color> c2 = colors.iterator(); c2.hasNext(); ) {    System.out.println(c+" "+c2.next());   }   }  }}

現在再來執行,就可以很好地得出16種結果了。這種方式也比較不錯,但是卻不能很好地解決問題1和問題2。因此,為了解決這一現象,大佬Joshua Bloch在書中提出,推薦使用for-each迴圈來代替for迴圈。

二、for-each迴圈

既然作者推薦使用for-each迴圈,我們看看他有什麼好處。是如何解決上面的問題的。

public class Main { //列舉顏色和尺寸 enum Color { RED, GREEN, BLUE, BLACK } enum Size { ONE, TWO, THREE, FOUR, FIVE,    SIX, SEVEN, EIGHT,NINE, TEN} //定義花 static class Flower{  public Flower(Color color, Size size) {} } public static void main(String[] args) {  Collection<Color> colors = Arrays.asList(Color.values());  Collection<Size> sizes = Arrays.asList(Size.values());  List<Flower> flowers = new ArrayList<>();  //for-each迴圈  for (Color color:colors) {   for (Size size:sizes ) {    flowers.add(new Flower(color, size));   }   }  }}

看裡面的for-each迴圈。上面的問題就全都解決了。好吧,可能你會感覺,就這?還有一個好處還沒說,再往下看。

for-each 迴圈不僅允許遍歷集合和陣列,還允許遍歷實現 Iterable 介面的任何物件,該介面由單個方法組成。接 口定義如下:

public interface Iterable<E> { // Returns an iterator over the elements in this iterable Iterator<E> iterator();}

如果必須從頭開始編寫自己的 Iterator 實現,那麼實現 Iterable 會有點棘手,但是如果你正在編寫表示一組元素 的型別,那麼你應該強烈考慮讓它實現 Iterable 介面,甚至可以選擇不讓它實現 Collection 介面。這允許使用者使用for-each 迴圈遍歷型別,他們會永遠感激不盡的 。

但是,有三種常見的情況是你不能分別使用 for-each 迴圈的:

(2)轉換:如果需要遍歷一個列表或陣列並替換其元素的部分或全部值,那麼需要列表迭代器或陣列索引來替換元素的值。

(3)並行迭代:如果需要並行地遍歷多個集合,那麼需要顯式地控制迭代器或索引變數,以便所有迭代器或索引變數都可以同步進行 。

如果發現自己處於這些情況中的任何一種,請使用傳統的 for 迴圈,並警惕本條目中提到的陷阱 。

11
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • BGP域內 EVPN方式VXLAN實驗(二)