Java中的 for-each 循环

470 阅读3分钟

引言

首先我们都知道 java 中遍历方式一共有三种,传统的 for - i 循环,迭代器循环,以及 for-each 循环, 这里我们重点介绍 for-each 循环。

原理

数组

首先我们来看一段代码

int[] ints = {1, 2, 3};
for (int anInt : ints) {
   System.out.println(anInt);
}

这时我们查看下编译后的代码

int[] ints = new int[]{1, 2, 3};
int[] var1 = ints;
int var2 = ints.length;

for(int var3 = 0; var3 < var2; ++var3) {
    int anInt = var1[var3];
    System.out.println(anInt);
}

可以很清晰的看见,数组的增强 for 循环本质上就是普通的 for - i 循环。

集合

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String s : list) {
   System.out.println(s);
}

查看编译后的代码

List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator var1 = list.iterator();

while(var1.hasNext()) {
    String s = (String)var1.next();
    System.out.println(s);
}

至此我们可以很清晰的看见使用 Iterator 进行遍历,所以本质上还是 第二种循环方式。

问题

那么使用 Iterator 进行遍历途中 如果进行 数据的删除会出现什么结果呢?

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String s : list) {
   if ("2".equals(s)) {
      list.remove(s);
   }
}

运行上述的代码 它会抛出 ConcurrentModificationException 异常,它为什么会产生呢,我们来追踪下。

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
   String next = iterator.next();
   if ("2".equals(next)) {
      list.remove(next);
   }
}

image.png 下面我们来分析这段代码,很显然 iteratorArrayList 里的一个内部类对象 Itr,这个 expectedModCount = 2, 是因为 ArraysList 中 的 ModCount 属性,它在调用 add 方法就会自增

image.png image.png

然后我们再看看 这个内部类对象 Itr 做了些什么 image.png 它把 modCount 赋值给了 expectionModCountcursor 表示下一个返回值的下标, lastRet = -1 表示 上一个返回值的下标 默认 -1

我们再来看看 iterator.next() 方法

image.png

这时我们可以看见 checktForComodification() 方法,跟进去看 image.png

显然 当 modCount != expectedModCount 它就会抛出 ConcurrentModificationException 异常, 这里我们也清楚了 Itr 对象 中的 this 指向的就是 list 对象,那么我们分析 list.remove()方法做了什么

image.png image.png 可以很清晰的看见 modCount++,总结一下就是 迭代器中的 expectedModCount = 2 然后 list.remove() 方法让 modCount 变成了 3 导致 expectedModCount != modCount 才抛出了异常。 很好,那么我们继续抛出下一个话题,把删除 元素 "2" 换成 "1" 呢?

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
   String next = iterator.next();
   if ("1".equals(next)) {
      list.remove(next);
   }
}

结果是不会抛出异常,并且能够正常的删除,这是为什么呢?其实这里就是取巧了,利用了 hasNext()方法规避了异常的发生,让我们来看看究竟发生了什么。

image.png

hasNext() 方法当 cursor 等于 size 的时候就不成立

image.png

结论 :第一次循环的时候 next() 方法让 cursor = 1, 此时删除元素 "1", list 的 size = 1,这时进入第二次循环,hasNext() 方法返回 false,终止运行。 同理 换成元素 "1" "2" "3" 删除 "2" 也可以成功,但是这种方法还是不可取,如果要在遍历的时候删除元素,必须使用 iterator 中的 remove()方法

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
   String next = iterator.next();
   if ("2".equals(next)) {
      iterator.remove();
   }
}
//这里我们可以简写成 list.removeIf("2"::equals)
 <==> 它们是等价的写法 关于 lambda 表达式 函数式编程 这里不多介绍

image.png

这里我们再继续看 next()方法, 可以看见 lastRet 赋值为 cursor后移前的值,然后再看看 iterator.remove()方法 做了什么 image.png

它首先删除了 list 中的 lastRet 所对应的元素,再把 cursor 赋值为 lastRet ,并把 lastRet 置为 -1, 同时修改了 expectedModCount 的值,这样也就避免了 ConcurrentModificationException 异常的发生 同时这也是最为推荐的一种方式。

最后

鉴于第一次写这种文章,如果上述中有任何的错误或者是总结的不到位的地方还恳请大佬们指出,欢迎进行交流~~