大剑无锋之ArrayList中使用增强for循环能删除元素吗?【面试推荐】
好久没写java代码,前几天面试被问到不少java的问题,其中一个接下来要说的。
先看几段代码。
第一段(集合中两个元素,判断条件是第一个元素)
ArrayList<String> list = new ArrayList<String>();list.add("george");list.add("georgedage");for (String s : list) {if ("george".equals(s)){list.remove(s);}}System.out.println(list);输出结果
[georgedage]第二段代码(集合中两个元素,判断条件是第二个元素)
ArrayList<String> list = new ArrayList<String>();list.add("george");list.add("georgedage");for (String s : list) {if ("georgedage".equals(s)){ list.remove(s);}}System.out.println(list);输出结果
再看第三段代码(集合中三个元素,判断条件是第一个元素)
ArrayList<String> list = new ArrayList<String>();list.add("george");list.add("georgedage");list.add("kangkang");for (String s : list) {if ("george".equals(s)){list.remove(s);}}System.out.println(list);输出结果
程序运行结果为:当集合中只有两个元素,且判断条件是第一个元素,remove方法执行成功
当集合中有两个元素,且判断条件是第二个元素,remove执行抛出ConcurrentModificationException的异常
当集合中有两个以上元素(不包含两个)时,判断条件就算为第一个元素,remove执行也抛出ConcurrentModificationException的异常
或许我们之前编写代码,或者看文档时有了解过,对于集合的增加、删除、修改元素,均不可以使用foreach(增强for循环)
那么为什么呢?
接下来就让我们走进他的内心世界!!!(源码来袭,请睁大双眼)
在进入ArrayList中,我发现有forEach这个方法,但是我们都知道增强for循环对于集合的话,只适用于实现Iterable接口的集合上。那么增强for的底层究竟是什么?进行一次反编译。
实现原理
可以看到,增强For是JAVA提供的语法糖,这里我们剖析一下,这种增强for循环底层是如何实现的。
我们对以下代码进行反编译:
| 1 2 3 | for (Integer i : list) { System.out.println(i); } |
反编译后:
| 1 2 3 4 | Integer i; for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){ i = (Integer)iterator.next(); } |
反编译后的代码其实比较复杂,我们按照执行顺序拆解一下:
Integer i; 定义一个临时变量i
Iterator iterator = list.iterator(); 获取List的迭代器
iterator.hasNext(); 判断迭代器中是否有未遍历过的元素
i = (Integer)iterator.next(); 获取第一个未遍历的元素,赋值给临时变量i
System.out.println(i) 输出临时变量i的值
如此循环往复,直到遍历完List中的所有元素。
通过反编译,我们看到,其实JAVA中的增强for循环底层是通过迭代器模式来实现的。
这也就说通我们上面代码中所踩的坑
既然增强for循环通过迭代器实现,那么必然有迭代器的特性。
Java中有fail-fast机制。在使用迭代器遍历元素的时候,在对集合进行删除的时候一定要注意,使用不当有可能发生ConcurrentModificationException,这是一种运行时异常,编译期并不会发生。只有在程序真正运行时才会爆发。
如以下代码:
| 1 2 3 4 | for (Student stu : students) { if (stu.getId() == 2) students.remove(stu); } |
会抛出ConcurrentModificationException异常。
Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。
在源码中这样展示:
private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public boolean hasNext() {return cursor != size;}@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];}在第一段代码中,为什么能remove成功呢,其实它只循环了一次,所以成功了。
因为它在remove元素1之后,它的size - 1变成1,然后Itr内部的cursor变量由0变成1
此时1 = 1,循环结束,所以成功了。
arraylist2为什么remove失败呢,因为他在循环第二次的时候,也remove成功了,但是第三次判断next的时候cursor的值为2导致不等于现在的size 1,所以执行了next方法,最重要的来了,之前remove的操作导致ArrayList的modCount值加1,然后Itr类中的expectedModCount保持不变,所以会抛出异常。
因此得出结论:
不允许在foreach中删除、增加、修改ArrayList中的元素。正确的在遍历的同时删除元素的姿势:
Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String item = iterator.next();if ("georgedage".equals(item)){iterator.remove();}}当然如果存在并发操作,还需要对Iterator进行加锁操作。
撤了撤了,如果还有什么好的建议,互相交流一下!!!
总结
以上是生活随笔为你收集整理的大剑无锋之ArrayList中使用增强for循环能删除元素吗?【面试推荐】的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 每日两SQL(5),欢迎交流~
- 下一篇: 浅析索引失效(一)