我是靠谱客的博主 阳光麦片,最近开发中收集的这篇文章主要介绍java foreach 源码_foreach深入理解--源码解析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1、概述:

foreach是用来循环遍历的方式之一,在java8中新增加的for循环的简化版,虽然说是简化版,并不是说比for或者iterator好用;

主要区别在于:

(1)fori是通过下标访问;

(2)foreach是通过容器的itrator的next()方法来迭代;

这篇文章主要来介绍foreach。

2、foreach样例展示

举例代码:

/**

* @ClassName TestForeach

* @Description TODO

* @Author zhaoyan

* @Date 2019/7/17 16:40

* @Version 1.0

**/

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

public class TestForeach {

List list = new ArrayList();

public TestForeach() {

this.list.add("北京");

this.list.add("上海");

this.list.add("广州");

this.list.add("深圳");

}

public static void main(String[] args) {

// ArraySplit();

// IteratorSplit();

new TestForeach().foreachSplit();

}

public void foreachSplit() {

for (String s : list) {

System.out.println(s);

}

}

}

运行结果如下:

北京

上海

广州

上述代码定义ArrayList集合,循环遍历输出。

执行代码,生成class文件,并进行反编译得到的代码(我是用IDEA,在IDEA的OUT目录下找到class文件,直接点开即可):

5a6bdce7bc3c

22.png

代码如下:

List list = new ArrayList();

list.add("北京");

list.add("上海");

list.add("广州");

Iterator iterator = list.iterator();

while(iterator.hasNext()) {

String s = (String)iterator.next();

System.out.println(s);

}

反编译可以知道,foreach底层是利用迭代器实现的,初始化获得迭代器,判断条件hasNext(),然后获得next(),最后打印这个结果。

另外foreach不支持在循环中添加删除操作,因为在使用foreach循环的时候数组(集合)就已经被锁定不能被修改,否则会报出java.util.ConcurrentModificationException异常;

但是数组的操作中Iterator是可以进行数组的操作的,这点怎么解释呢?

下面来说明一下这点!!!

3、foreach的remove

上车,飞一波,粘贴代码!!!

public static void foreachSplit() {

List list = new ArrayList();

list.add("北京");

list.add("上海");

list.add("广州");

list.add("深圳");

for (String s : list) {

System.out.println(s);

if ("北京".equals(s)) {

list.remove(s);

}

}

}

运行结果如下:

北京

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)

at java.util.ArrayList$Itr.next(ArrayList.java:851)

at com.zhaoyan.foreach.TestForeach.foreachSplit(TestForeach.java:47)

at com.zhaoyan.foreach.TestForeach.main(TestForeach.java:19)

说明在迭代器使用过程中,禁止修改迭代器中的内容。否侧会抛出java.util.ConcurrentModificationException。

iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时自动增减expectedModCount,这样就导致两个count不相等,从而抛出异常。

4、重头戏,源码解析

反编译可以知道,foreach底层是利用迭代器实现的,因此:

Iterator var1 = list.iterator();

package java.util

/**

* Returns an iterator over the elements in this list in proper sequence.

*

*

The returned iterator is fail-fast.

*

* @return an iterator over the elements in this list in proper sequence

*/

public Iterator iterator() {

return new Itr();

}

iterator 这个方法在ArrayList和AbstractList中都存在,但AbstractList是抽象类,是被继承之后,要拥有具体的实现方法,抽象类的方法是一定要进行实现的,具体区分关注我之前写的抽象类的博客:https://blog.csdn.net/ITzhaoyan/article/details/94599218

/**

* An optimized version of AbstractList.Itr

*/

private class Itr implements Iterator {

int cursor; // index of next element to return

int lastRet = -1; // index of last element returned; -1 if no such

int expectedModCount = modCount;

cursor:要返回的下一个元素的索引,下一个遍历到的元素的下标,目前还没有开始遍历,所以cursor是0

lastRet :上一次操作的元素的下标,初始值为-1。

expectedModCount :表示迭代器对集合进行修改的次数,是实际上应该轮询的次数;

modCount:modCount是ArrayList的父类AbstractList的一个字段,这个字段的含义是list结构发生变更的次数,通常是add或remove等导致元素数量变更的会触发modCount++。

iterator.hasNext()

public boolean hasNext() {

return cursor != size;

}

size:是集合的大小;

指针的下一个元素还有的话,逻辑为真,没有元素时,循环条件为假,退出循环。

用while进行循环,hasnext作为退出条件进行轮询集合;然后执行下面代码:

String s = (String)iterator.next();

@SuppressWarnings("unchecked")

public E next() {

checkForComodification();

int i = cursor;

if (i >= size)

throw new NoSuchElementException();

Object[] elementData = ArrayList.this.elementData;

if (i >= elementData.length)

throw new ConcurrentModificationException();

cursor = i + 1;

return (E) elementData[lastRet = i];

}

//final 修饰的方法,不能被重写覆盖。

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

在调用next()方法的时候,先执行checkForComodification()方法,方法中判断

modCount != expectedModCount,在上述代码中能够肯定,当进行remove操作的时候,

会报java.util.ConcurrentModificationException,就是这段代码的作用。

迭代器内部的每次遍历都会记录List内部的modcount当做预期值,然后在每次循环中用预期值与List的成员变量modCount作比较,但是普通list.remove调用的是List的remove,这时modcount++,但是iterator内记录的预期值=并没有变化,所以会报错。

然后执行获取该下标的数据,并移动元素下标,结果数据进行返回。

5、foreach remove的坑

public static void foreachSplit() {

List list = new ArrayList();

list.add("北京");

list.add("上海");

list.add("广州");

list.add("深圳");

for (String s : list) {

System.out.println(s);

if ("广州".equals(s)) {

list.remove(s);

}

}

}

结果如下:

北京

上海

广州

Process finished with exit code 0

哎,什么情况,竟然能够删除成功!!!

这就是foreach remove的坑。

删除倒数第二个元素的时候,cursor指向最后一个元素的,而此时删掉了倒数第二个元素后,cursor和list.size()正好相等了,所以hasNext()返回false,遍历结束,这样就成功的删除了倒数第二个元素了。这里面的重点是没有执行next()中的checkForComodification()方法,就直接退出了;

6、正确的操作

6.1、fori删除

下面看一段代码:

public void foreachRemove() {

for (int i = 0; i < list.size(); i++) {

System.out.println(list.get(i));

list.remove(i);

}

}

这段代码能删除,但是结果如下

北京

广州

Process finished with exit code 0

原来的元素1被remove后,后面的向前拷贝,2到了原来1的位置(下标0),3到了原来2的位置(下标1),size由3变2,i+1=1,输出list.get(1)就成了3,2被漏掉了。同理,每执行一次就会漏掉一个元素;

所以用fori进行元素的删除,我们在操作删除之后,要把下标的位置进行前移,这样就能无遗漏的进行元素删除了。

public void foreachRemove() {

for (int i = 0; i < list.size(); i++) {

System.out.println("删除的元素为:--"+list.get(i));

list.remove(i);

i--;

}

}

执行结果如下:

删除的元素为:--北京

删除的元素为:--上海

删除的元素为:--广州

删除的元素为:--深圳

Process finished with exit code 0

6.2、Iterator迭代器的remove

代码如下:

public void iRemove() {

Iterator itr = list.iterator();

while (itr.hasNext()) {

String s = itr.next();

System.out.println(s);

itr.remove();

}

}

执行结果如下:

北京

上海

广州

深圳

Process finished with exit code 0

迭代器的源码如下:

public void remove() {

if (lastRet < 0)

throw new IllegalStateException();

checkForComodification();

try {

ArrayList.this.remove(lastRet);

cursor = lastRet;

lastRet = -1;

expectedModCount = modCount;

} catch (IndexOutOfBoundsException ex) {

throw new ConcurrentModificationException();

}

}

看源码了解依然有checkForComodification()校验,但是在remove()之后又重新赋值expectedModCount = modCount;,所以校验是通过的。

7、总结

没啥说的了,以上的分享都是单线程的情况,多线程的情况后续分享,

互相学习,感激每个奋战在一线的开发人员,信息化的进步有你们的贡献!!!

最后

以上就是阳光麦片为你收集整理的java foreach 源码_foreach深入理解--源码解析的全部内容,希望文章能够帮你解决java foreach 源码_foreach深入理解--源码解析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(74)

评论列表共有 0 条评论

立即
投稿
返回
顶部