Effective Java学习(通用程序设计)之——for-each循环优先于传统的for循环

在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循环,要警惕本条目中提到的陷阱,并且要确保做到最好。

你可能感兴趣的:(java)