還在用for循環遍歷元素?試試for-each,它不香嗎?

本文基於《Effective java》第58條

在此基礎之上加入了本身的理解。java

image

for循環是平時寫代碼用的最多的,可是以前看《Effective java》,大佬在某些場景寫並不推薦。結合着本身以前刷算法題的經歷,受益不淺。算法

1、for循環的缺點

在以往遍歷元素的時候,咱們一般採用如下的形式:數組

`public class Main {
 public static void main(String[] args) {
  //一、數組元素
  int[] num = new int[] {1,2,3,4,5};
  //數組的遍歷
  for(int i=0;i<num.length;i++) 
   System.out.println(num[i]);
  //二、對象元素
  ArrayList<Person> lists = new ArrayList<>();
  lists.add(new Person("張三"));
  lists.add(new Person("李四"));
  lists.add(new Person("愚公要移山"));
  //對象元素的遍歷
  for(Iterator<Person> it=lists.iterator();it.hasNext();) {
   Person p = it.next();
   System.out.println(p.getName());
  }
 }
}
`緩存

這種寫法看起來還不錯,可是卻並不完美。咱們來分析一下,有什麼缺點。而後給出解決方案。this

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

從上面兩種遍歷的代碼上來看,對於數組元素是經過索引i來遍歷的,可是整個for循環出現了四次i,對於對象元素是經過迭代器it來遍歷的,可是整個for循環出現了三次it。在for循環遍歷元素的時候,就有屢次機會使用了錯誤的變量。並且有時候這些錯誤編譯器沒法發現。對整個應用系統形成沒法預知的錯誤。spa

問題2:遍歷對象元素時,須要注意容器類型

好比咱們這裏使用的是list,固然還有多是其餘容器類型,這些類型在更改時比較麻煩。對象

問題3:嵌套迭代拋出異常

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

`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 RED
GREEN GREEN
BLUE BLUE
BLACK 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循環。

2、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 循環的:

(1)有損過濾(Destructive filtering):若是須要遍歷集合,並刪除指定選元素,則須要使用顯式迭代器,以即可以調用其 remove 方法。 一般可使用在 Java 8 中添加的 Collection 類中的 removeIf 方法,來避免顯式遍歷。

(2)轉換:若是須要遍歷一個列表或數組並替換其元素的部分或所有值,那麼須要列表迭代器或數組索引來替換元素的值。

(3)並行迭代:若是須要並行地遍歷多個集合,那麼須要顯式地控制迭代器或索引變量,以便全部迭代器或索引變量均可以同步進行 。

若是發現本身處於這些狀況中的任何一種,請使用傳統的 for 循環,並警戒本條目中提到的陷阱 。

相關文章
相關標籤/搜索