在java1.5发行之前,对于集合进行遍历的首选做法如下:
for(Iterator i = c.iterator(); i.hasNext();){ i.next(); }
遍历数组的首先做法是:
for(int i = 0; i < a.length; i++){ a[i]; }
这些做法都比while循环更好,但是他们并不完美。迭代器和索引变量都会造成一些混乱。而且,他们也代表着出错的可能。迭代器和索引变量在每个循环中出现三次,其中有两次让你很容易出错,一旦出错,就无法保证编译器能够发现错误。
java1.5之后引入了for-each循环,通过完全隐藏的迭代器或者索引变量,避免了混乱和出错的可能,这种模式同样使用与集合和数组
for(Element e : elements){ }
当你到冒号 :时,可以把他读作“在.....里面“。因此上面的循环可以读作”对于元素中的每一个元素e"注意,利用for-each循环不会有性能损失,甚至用于数组也是一样,实际上,在某些情况下,比起普通的for循环,他还稍微有些性能优势,因为他对数组索引的边界值只计算一次。虽然可以手工完成这项工作,但是程序员并不总是这么做。
在对多个集合进行嵌套循环迭代时,for-each循环相对于普通的for循环优势会更加明显,下面就是试图对两个集合进行嵌套循环时经常会犯的错误:
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())); } } public class Card { public Card(Suit next, Rank next2) { System.out.println(next+" " + next2); } }
如果之前没有发现bug也不必难过,许多专家级的程序员偶尔也会犯这样的错误。问题在于,在这个迭代器上对于外部的集合调用了太多次的next方法了,他应该从外部的循环进行调用,以便于每种花色调用一次,但它却是从内部循环调用的,因此他是每张牌调用一次。在用完所有花色之后,循环就会抛出NullPointerException异常。
如果真的那么不幸,并且外部集合的大小是内部集合大小的几倍——可能因为它们是相同的集合——循环就会正常终止,但是不会完成你想要的工作,例如:下面是个考虑不周的尝试,要打印一对骰子的所有 可能的滚动:
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()); } }
这段程序不会抛出任何异常,而是打印出6对重复的词(从ONE ONE到SIX SIX)而不是预计的36中组合。
为了修正这个bug,必须在外部循环的作用域中添加一个变量来保存外部元素:
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)); } }
for-each循环不仅让你遍历集合和数组,还让你遍历任何实现Iterable接口的对象。这个简单的接口有单个方法组成,与for-each循环同时被添加到java平台,下面就是这个接口的示例:
public interface Iterable<E>{ Iterable<E> iterator(); }
实现Iterable接口并不能,如果你在编写的类型表示的是一组元素,即使你选择不让他实现Collection,也要让它实现Iterable接口,这样可以允许用户利用for-each循环遍历你的类型,会令用户永远感激不尽的。
总之 ,for-each循环在简洁性和放bug方面有着传统for循环无法比拟的优势,并且没有性能损失,应该尽可能的使用for-each循环。遗憾的是,有三种常见的情况布恩无法使用for-each循环:
过滤 ——如果需要遍历集合,并删除选定的元素,就需要使用显示的迭代器,比便于可以调用他的remove方法。
转换——如果需要遍历的列表或者数组,并取代他部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值。
平行迭代——如果需要并行的遍历多个集合,就需要显示的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移。
在以上任何一种情况下,就要使用普通的for循环,要警惕本条目中提到的陷阱,并且要确保做到最好。