Java高效编码:for-each循环优先于传统的for循环

版本1.5之前,在集合上迭代优先考虑的习惯用法如下:

// No longer the preferred idiom to iterate over a collection!

for (Iterator i = c.iterator(); i.hasNext(); ) {

       doSomething((Element) i.next()); // (No generics before 1.5)

}

在数组上迭代,则优先考虑下面的方法:

// No longer the preferred idiom to iterate over an array!

for (int i = 0; i < a.length; i++) {

       doSomething(a[i]);

}

这些习惯用法要好于while循环(Item 45),但也不完美。代迭器与索引都有些混乱。而且,它们还可能引起错误。在上面的循环中迭代器与索引都出现了三次,其中有两个地方可能带来错误,如果的确出现了这种错误,却无法保证编译器能捕获到这些错误。

       版本1.5引入的for-each循环从混乱中解脱出来了,而且完全摆脱了隐藏在迭代器与索引变量后的可能的出错的机会。这种惯用法对集合与数组都适用:

// The preferred idiom for iterating over collections and arrays

for (Element e : elements) {

       doSomething(e);

}

 

当碰到冒号(:)时,可读成”in.”,因此,上面的循环读成”对在(in)elements中的每一个元素e“。注意,即便对数组,使用for-each循环也没有性能上的损害。事实上,在某些情形,它的性能还稍微优于传统的for循环,因为它只计算数组上限一次。虽然你可以做得到,但程序员未必总是这样去做(Item 45)。

       在多个集合的嵌套循环上,for-each循环更优与传统的for循环。下面是一个在两个集合上嵌套循环时常见的一个错误:

 

// Can you spot the bug?

enum Suit { CLUB, DIAMOND, HEART, SPADE }

enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,

NINE, TEN, JACK, QUEEN, KING }

...

Collection<Suit> suits = Arrays.asList(Suit.values());

Collection<Rank> ranks = Arrays.asList(Rank.values());

List<Card> deck = new ArrayList<Card>();

for (Iterator<Suit> i = suits.iterator(); i.hasNext(); )

       for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )

              deck.add(new Card(i.next(), j.next()));

 

       如果你没有发现这个bug也不用沮丧,许多专家级的程序员也时不时的犯这种错误。问题出在调用了太多的外层集合(suits)迭代器上的next方法。本来它应该在外层循环里被调用,这样每个suit调用一次,然而,现在它在内层循环中被调用,变成了每个card调用一次。在你运行完suits,循环会抛出NoSuchElementException.

       如果你很不幸,外层集合的长度是内层循环的倍数-或许因为它们是相同的集合-循环会正常中止,但结果却不是你想要的。例如,考虑下面有问题的代码,它企图打印所有可能的成对骰子数。

 

// Same bug, different symptom!

enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX }

...

Collection<Face> faces = Arrays.asList(Face.values());

for (Iterator<Face> i = faces.iterator(); i.hasNext(); )

       for (Iterator<Face> j = faces.iterator(); j.hasNext(); )

              System.out.println(i.next() + " " + j.next());

 

这段程序不会抛出异常,但仅打印出六对数(从”ONE ONE”到”SIX SIX”),而不是所希望的36种组合。

       为了修改这些例子中的bug,必须在外层循环中增加一个变量,以保持外围元素。

// Fixed, but ugly - you can do better!

for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) {

       Suit suit = i.next();

       for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )

              deck.add(new Card(suit, j.next()));

}

       相反地,如果你使用了for-each循环,问题就消失了。代码也如你所希望的简洁。

// Preferred idiom for nested iteration on collections and arrays

for (Suit suit : suits)

       for (Rank rank : ranks)

              deck.add(new Card(suit, rank));

 

       for-each不仅可在集合和数组上迭代,而且还可在任何实现了Iterable接口的对象上迭代。接口Iterablel有一个简单的方法,随for-each一起加入平台,接口如下:

public interface Iterable<E> {

       // Returns an iterator over the elements in this iterable

       Iterator<E> iterator();

}

       实现这个接口并不困难。如果所写的类型代表一组元素,即便不让他实现Collection接口也应该让它实现Iterable接口。这会让你的用户可以通过for-each循环在你的类型上迭代,你的用户会永远感谢你。

 

       总之,与传统的for循环相比,在简洁及防错方面,for-each循环有巨大的优势,而且没有性能损耗。只要可以使用就应该用之。不幸的是,有三种普遍情况无法使用for-each循环:

1、Filtering-如果需要在集合上遍历且移去选定的元素,就要使用显式的迭代,并调用它的remove方法。

2、Transforming-如果需要在list或数组上遍历且要替换部分或所有的元素值,则需要list的迭代器或数组的索引去设置这些值。

3、Parallel iteration-如果需要并行的遍历多个集合,则需要显式的控制迭代器或索引变量,以便所有的迭代器或索引能协同推进(如上面的有问题的card和dice例子所示)。

如果你发现自已遇到的是上面所述的情形,就使用普通的for循环,并提防本条目所提到的陷阱,相信自已能做到最好。

你可能感兴趣的:(java,generics,Collections,iterator,nested,parallel)