mirror of
https://github.com/sjsdfg/effective-java-3rd-chinese.git
synced 2024-12-28 05:40:45 +08:00
for-each 循环优于传统 for 循环
This commit is contained in:
parent
064073a931
commit
3d236d6c6e
108
docs/notes/58. for-each 循环优于传统 for 循环.md
Normal file
108
docs/notes/58. for-each 循环优于传统 for 循环.md
Normal file
@ -0,0 +1,108 @@
|
||||
# 58. for-each 循环优于传统 for 循环
|
||||
|
||||
正如在条目 45 中所讨论的,一些任务最好使用 Stream 来完成,一些任务最好使用迭代。下面是一个传统的 for 循环来遍历一个集合:
|
||||
|
||||
```java
|
||||
// Not the best way to iterate over a collection!
|
||||
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
|
||||
Element e = i.next();
|
||||
... // Do something with e
|
||||
}
|
||||
```
|
||||
|
||||
下面是迭代数组的传统 for 循环的实例:
|
||||
|
||||
```java
|
||||
// Not the best way to iterate over an array!
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
... // Do something with a[i]
|
||||
}
|
||||
```
|
||||
|
||||
这些习惯用法比 while 循环更好(详见第 57 条),但是它们并不完美。迭代器和索引变量都很混乱——你只需要元素而已。此外,它们也代表了出错的机会。迭代器在每个循环中出现三次,索引变量出现四次,这使你有很多机会使用错误的变量。如果这样做,就不能保证编译器会发现到问题。最后,这两个循环非常不同,引起了对容器类型的不必要注意,并且增加了更改该类型的小麻烦。
|
||||
|
||||
for-each 循环 (官方称为“增强的 for 语句”) 解决了所有这些问题。它通过隐藏迭代器或索引变量来消除混乱和出错的机会。由此产生的习惯用法同样适用于集合和数组,从而简化了将容器的实现类型从一种转换为另一种的过程:
|
||||
|
||||
```java
|
||||
// The preferred idiom for iterating over collections and arrays
|
||||
for (Element e : elements) {
|
||||
... // Do something with e
|
||||
}
|
||||
```
|
||||
|
||||
当看到冒号 (:) 时,请将其读作“in”。因此,上面的循环读作“对于元素 elements 中的每个元素 e”。“使用 for-each 循环不会降低性能,即使对于数组也是如此:它们生成的代码本质上与手工编写的代码相同。
|
||||
|
||||
当涉及到嵌套迭代时,for-each 循环相对于传统 for 循环的优势甚至更大。下面是人们在进行嵌套迭代时经常犯的一个错误:
|
||||
|
||||
```java
|
||||
// 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 }
|
||||
...
|
||||
static Collection<Suit> suits = Arrays.asList(Suit.values());
|
||||
static Collection<Rank> ranks = Arrays.asList(Rank.values());
|
||||
|
||||
List<Card> deck = new ArrayList<>();
|
||||
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,也不必感到难过。许多专业程序员都曾犯过这样或那样的错误。问题是,对于外部集合 (suit),next 方法在迭代器上调用了太多次。它应该从外部循环调用,因此每花色调用一次,但它是从内部循环调用的,因此每一张牌调用一次。在 suit 用完之后,循环抛出 `NoSuchElementException` 异常。
|
||||
|
||||
如果你真的不走运,外部集合的大小是内部集合大小的倍数——也许它们是相同的集合——循环将正常终止,但它不会做你想要的。 例如,考虑这种错误的尝试,打印一对骰子的所有可能的掷法:
|
||||
|
||||
```java
|
||||
// Same bug, different symptom!
|
||||
enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX }
|
||||
...
|
||||
Collection<Face> faces = EnumSet.allOf(Face.class);
|
||||
|
||||
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 个组合。
|
||||
|
||||
要修复例子中的错误,必须在外部循环的作用域内添加一个变量来保存外部元素:
|
||||
|
||||
```java
|
||||
/ 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 循环,问题就会消失。生成的代码也尽可能地简洁:
|
||||
|
||||
```java
|
||||
// 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 循环的:
|
||||
|
||||
- **有损过滤(Destructive filtering)**——如果需要遍历集合,并删除指定选元素,则需要使用显式迭代器,以便可以调用其 remove 方法。 通常可以使用在 Java 8 中添加的 Collection 类中的 removeIf 方法,来避免显式遍历。
|
||||
- **转换**——如果需要遍历一个列表或数组并替换其元素的部分或全部值,那么需要列表迭代器或数组索引来替换元素的值。
|
||||
- **并行迭代**——如果需要并行地遍历多个集合,那么需要显式地控制迭代器或索引变量,以便所有迭代器或索引变量都可以同步进行 (正如上面错误的 card 和 dice 示例中无意中演示的那样)。
|
||||
|
||||
如果发现自己处于这些情况中的任何一种,请使用传统的 for 循环,并警惕本条目中提到的陷阱。
|
||||
|
||||
for-each 循环不仅允许遍历集合和数组,还允许遍历实现 Iterable 接口的任何对象,该接口由单个方法组成。接口定义如下:
|
||||
|
||||
```java
|
||||
public interface Iterable<E> {
|
||||
// Returns an iterator over the elements in this iterable
|
||||
Iterator<E> iterator();
|
||||
}
|
||||
```
|
||||
|
||||
如果必须从头开始编写自己的 Iterator 实现,那么实现 Iterable 会有点棘手,但是如果你正在编写表示一组元素的类型,那么你应该强烈考虑让它实现 Iterable 接口,甚至可以选择不让它实现 Collection 接口。这允许用户使用 for-each 循环遍历类型,他们会永远感激不尽的。
|
||||
|
||||
总之,for-each 循环在清晰度,灵活性和错误预防方面提供了超越传统 for 循环的令人注目的优势,而且没有性能损失。 尽可能使用 for-each 循环优先于 for 循环。
|
Loading…
Reference in New Issue
Block a user