第46条:for-each循环优先于传统的for循环
for-each循环通过完全隐藏迭代器或者索引变量,避免混乱和出错的可能,适用于集合和数组和任何实现Iterable接口的对象。
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。
如果你很不幸,外层集合的长度是内层集合大小的几倍–可能因为它们是相同的集合–循环会正常中止,但结果却不是你想要的。例如,考虑下面有问题的代码,它企图打印所有可能的成对骰子数。
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());
期望打印预计的36中组合,结果却是6个重复的词。
修正方法:
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循环更简洁:
for (Suit suit : suits)
for (Rank rank : ranks)
deck.add(new Card(suit, rank));
or-each不仅可在集合和数组上迭代,而且还可在任何实现了Iterable接口的对象上迭代。接口Iterablel有一个简单的方法,随for-each一起加入平台,接口如下:
public interface Iterable<E> {
Iterator<E> iterator();
}
实现Iterable接口并不困难。如果所写的类型代表一组元素,即便不让他实现Collection接口也应该让它实现Iterable接口。这会让你的用户可以通过for-each循环在你的类型上迭代,你的用户会永远感谢你。
总之,使用嵌套for-each循环更简洁,代码优雅,性能略胜于普通for循环,因为它对数组索引的边界值只计算一次。
无法使用for-each循环的情况:
1.过滤 ,需要遍历集合并删除选定的元素,需要显式的迭代器,以便调用它的remove方法。
2.转换 ,需要遍历列表或者数组,并取代它部分或者全部元素值,需要列表迭代器或者数组索引,以便设定元素的值。
3.平行迭代 ,并行地遍历多个集合,需要显式地控制迭代器或者索引变量,以便所有迭代器和索引变量都可以得到同步前移(像上述第一个代码 块那样)。