作者:你呀不牛
链接:https://juejin.cn/post/7114669787870920734
前段时间,同事在代码中KW扫描的时候出现这样一条:
上面出现这样的原因是在使用foreach对HashMap进行遍历时,同时进行put赋值操作会有问题,异常ConcurrentModificationException。
于是帮同简单的看了一下,印象中集合类在进行遍历时同时进行删除或者添加操作时需要谨慎,一般使用迭代器进行操作。
于是告诉同事,应该使用迭代器Iterator来对集合元素进行操作。同事问我为什么?这一下子把我问蒙了?对啊,只是记得这样用不可以,但是好像自己从来没有细究过为什么?
于是今天决定把这个HashMap遍历操作好好地研究一番,防止采坑!
foreach循环?
java foreach 语法是在jdk1.5时加入的新特性,主要是当作for语法的一个增强,那么它的底层到底是怎么实现的呢?下面我们来好好研究一下:
foreach 语法内部,对collection是用iterator迭代器来实现的,对数组是用下标遍历来实现。Java 5 及以上的编译器隐藏了基于iteration和数组下标遍历的内部实现。
(注意,这里说的是“Java编译器”或Java语言对其实现做了隐藏,而不是某段Java代码对其实现做了隐藏,也就是说,我们在任何一段JDK的Java代码中都找不到这里被隐藏的实现。这里的实现,隐藏在了Java 编译器中,查看一段foreach的Java代码编译成的字节码,从中揣测它到底是怎么实现的了)
我们写一个例子来研究一下:
public class HashMapIteratorDemo { String[] arr = {"aa", "bb", "cc"}; public void test1() { for(String str : arr) { } } }
将上面的例子转为字节码反编译一下(主函数部分):
也许我们不能很清楚这些指令到底有什么作用,但是我们可以对比一下下面段代码产生的字节码指令:
public class HashMapIteratorDemo2 { String[] arr = {"aa", "bb", "cc"}; public void test1() { for(int i = 0; i < arr.length; i++) { String str = arr[i]; } } }
看看两个字节码文件,有木有发现指令几乎相同,如果还有疑问我们再看看对集合的foreach操作:
通过foreach遍历集合:
public class HashMapIteratorDemo3 { List<Integer> list = new ArrayList<>(); public void test1() { list.add(1); list.add(2); list.add(3); for(Integer var : list) { } } }
通过Iterator遍历集合:
public class HashMapIteratorDemo4 { List<Integer> list = new ArrayList<>(); public void test1() { list.add(1); list.add(2); list.add(3); Iterator<Integer> it = list.iterator(); while(it.hasNext()) { Integer var = it.next(); } } }
将两个方法的字节码对比如下:
我们发现两个方法字节码指令操作几乎一模一样;
这样我们可以得出以下结论:
对集合来说,由于集合都实现了Iterator迭代器,foreach语法最终被编译器转为了对Iterator.next()的调用;
对于数组来说,就是转化为对数组中的每一个元素的循环引用。