乐趣区

关于java:还在用for循环遍历元素试试foreach它不香吗

本文基于《Effective java》第 58 条

在此基础之上退出了本人的了解。

for 循环是平时写代码用的最多的,然而之前看《Effective java》,大佬在某些场景写并不举荐。联合着本人之前刷算法题的经验,受益匪浅。

一、for 循环的毛病

在以往遍历元素的时候,咱们通常采纳以下的模式:

`public class Main {
 public static void main(String[] args) {
  //1、数组元素
  int[] num = new int[] {1,2,3,4,5};
  // 数组的遍历
  for(int i=0;i<num.length;i++) 
   System.out.println(num[i]);
  //2、对象元素
  ArrayList<Person> lists = new ArrayList<>();
  lists.add(new Person(“ 张三 ”));
  lists.add(new Person(“ 李四 ”));
  lists.add(new Person(“ 愚公要移山 ”));
  // 对象元素的遍历
  for(Iterator<Person> it=lists.iterator();it.hasNext();) {
   Person p = it.next();
   System.out.println(p.getName());
  }
 }
}
`

这种写法看起来还不错,然而却并不完满。咱们来剖析一下,有什么毛病。而后给出解决方案。

问题 1:迭代器或索引屡次呈现,容易造成应用谬误

从下面两种遍历的代码上来看,对于数组元素是通过索引 i 来遍历的,然而整个 for 循环呈现了四次 i,对于对象元素是通过迭代器 it 来遍历的,然而整个 for 循环呈现了三次 it。在 for 循环遍历元素的时候,就有屡次机会应用了谬误的变量。而且有时候这些谬误编译器无奈发现。对整个利用零碎造成无奈预知的谬误。

问题 2:遍历对象元素时,须要留神容器类型

比方咱们这里应用的是 list,当然还有可能是其余容器类型,这些类型在更改时比拟麻烦。

问题 3:嵌套迭代抛出异样

这种状况比较复杂一些,先来搞个例子。比如说,咱们想要列举每种花,这些花有两种属性一种是色彩,一种是大小。

`public class Main {
 // 枚举色彩和尺寸
 enum Color {RED, GREEN, BLUE, BLACK}
 enum Size {ONE, TWO, THREE, FOUR, FIVE,
    SIX, SEVEN, EIGHT,NINE, TEN}
 // 定义花
 static class Flower{
  public Flower(Color color, Size size) {}
 }
 public static void main(String[] args) {
  Collection<Color> colors = Arrays.asList(Color.values());
  Collection<Size> sizes = Arrays.asList(Size.values());
  List<Flower> flowers = new ArrayList<>();
  //for 循环增加所有的花和尺寸
  for (Iterator<Color> color = colors.iterator(); color.hasNext();) {
   for (Iterator<Size> size = sizes.iterator(); size.hasNext();) {
    flowers.add(new Flower(color.next(), size.next()));
   } 
  } 
 }
}
`

看似人畜有害,当初咱们运行一波。

`Exception in thread “main” java.util.NoSuchElementException
 at java.util.AbstractList$Itr.next(Unknown Source)
 at com.f2.Main.main(Main.java:25)
`

是不是感觉有点奇怪,如同双重循环遍历没啥问题,然而呈现了异样,起因是内部的 Color 迭代器调用了屡次,第一层 for 循环被调用了,然而又在第二层 for 循环外部被调用了,所以 color 的 next 被调用完了。所以呈现了 NoSuchElementException。然而有时候也不会呈现这种状况,场景是内部循环迭代器调用的次数刚好是外部调用的 n 倍。

问题 4:嵌套迭代不抛异样,然而后果不正确

这种状况是内部循环迭代器调用的次数刚好是外部调用的 n 倍。咱们再来个例子:

`public class Main {
 // 枚举色彩
 enum Color {RED, GREEN, BLUE, BLACK} 
 public static void main(String[] args) {
  Collection<Color> colors = Arrays.asList(Color.values());
  // 两层 for 循环
  for (Iterator<Color> c1 = colors.iterator(); c1.hasNext();) {
   for (Iterator<Color> c2 = colors.iterator(); c2.hasNext();) {
    System.out.println(c1.next()+” “+c2.next());
   } 
  } 
 }
}
`

当初对色彩进行 for 循环遍历,一共两层 for 循环,因为一共有四种色彩,两层 for 循环应该是打印 16 个后果。当初运行一遍看看后果:

`RED RED
GREEN GREEN
BLUE BLUE
BLACK BLACK
`

没错,的确是打印了四条。起因和 问题三 是一样的。有一种形式能够很好地解决这种嵌套的问题。

嵌套迭代问题解决:

间接看代码。既然是内部的迭代器 it 在外部应用了,那我在外部和内部之间用一个变量缓存起来不久好了。

`public class Main {
 // 枚举色彩
 enum Color {RED, GREEN, BLUE, BLACK}
 public static void main(String[] args) {
  Collection<Color> colors = Arrays.asList(Color.values());
  //for 循环
  for (Iterator<Color> c1 = colors.iterator(); c1.hasNext();) {
   // 用一个变量缓存起来
   Color c = c1.next();
   for (Iterator<Color> c2 = colors.iterator(); c2.hasNext();) {
    System.out.println(c+” “+c2.next());
   } 
  } 
 }
}
`

当初再来运行,就能够很好地得出 16 种后果了。这种形式也比拟不错,然而却不能很好地解决问题 1 和问题 2。因而,为了解决这一景象,大佬 Joshua Bloch 在书中提出,举荐应用 for-each 循环来代替 for 循环。

二、for-each 循环

既然作者举荐应用 for-each 循环,咱们看看他有什么益处。是如何解决下面的问题的。

`public class Main {
 // 枚举色彩和尺寸
 enum Color {RED, GREEN, BLUE, BLACK}
 enum Size {ONE, TWO, THREE, FOUR, FIVE,
    SIX, SEVEN, EIGHT,NINE, TEN}
 // 定义花
 static class Flower{
  public Flower(Color color, Size size) {}
 }
 public static void main(String[] args) {
  Collection<Color> colors = Arrays.asList(Color.values());
  Collection<Size> sizes = Arrays.asList(Size.values());
  List<Flower> flowers = new ArrayList<>();
  //for-each 循环
  for (Color color:colors) {
   for (Size size:sizes) {
    flowers.add(new Flower(color, size));
   } 
  } 
 }
}
`

看外面的 for-each 循环。下面的问题就全都解决了。好吧,可能你会感觉,就这?还有一个益处还没说,再往下看。

for-each 循环不仅容许遍历汇合和数组,还容许遍历实现 Iterable 接口的任何对象,该接口由单个办法组成。接 口定义如下:

`public interface Iterable<E> {
 // Returns an iterator over the elements in this iterable
 Iterator<E> iterator();
}
`

如果必须从头开始编写本人的 Iterator 实现,那么实现 Iterable 会有点辣手,然而如果你正在编写示意一组元素 的类型,那么你应该强烈思考让它实现 Iterable 接口,甚至能够抉择不让它实现 Collection 接口。这容许用户应用 for-each 循环遍历类型,他们会永远感激不尽的。

然而,有三种常见的状况是你不能别离应用 for-each 循环的:

(1)有损过滤(Destructive filtering):如果须要遍历汇合,并删除指定选元素,则须要应用显式迭代器,以便能够调用其 remove 办法。通常能够应用在 Java 8 中增加的 Collection 类中的 removeIf 办法,来防止显式遍历。

(2)转换:如果须要遍历一个列表或数组并替换其元素的局部或全副值,那么须要列表迭代器或数组索引来替换元素的值。

(3)并行迭代:如果须要并行地遍历多个汇合,那么须要显式地管制迭代器或索引变量,以便所有迭代器或索引变量都能够同步进行。

如果发现自己处于这些状况中的任何一种,请应用传统的 for 循环,并警觉本条目中提到的陷阱。

退出移动版