一般在遍历java集合的时候有三种方式:for-loop、增强for和iterator
public static void forEnhancedMethod(){
for (String str: list){
System.out.println(str);
}
}
public static void forLoopMethod(){
for (int i=0; i < list.size(); i++)
System.out.println(list.get(i));
}
}
public static void iteratorMethod(){
Iterator<String> itr = list.listIterator();
while (itr.hasNext()){
String el = itr.next();
System.out.println(el);
}
}
使用iterator遍历集合的时候是有fail-fast
机制的,也就是在遍历的时候,如果集合的结构发生变化,就会抛出ConcurrentModificationException
异常,那么问题来了,就比较一下有一种方式遍历方式在改变了结构的情况下到底有哪些是会抛出ConcurrentModificationException
异常的
先定义一个集合
public class ForeachIterator {
public static final ArrayList<String> list =
new ArrayList(Arrays.asList("a", "b", "c", "d", "e"));
//....
}
首先要明白ArrayList
内部存储数据是由Object[] elementData
数组维护的
我们准备删除c
数据:
public static void forLoopMethod(){
for (int i=0; i < list.size(); i++){
if (i == 2){
list.remove(i);
System.out.println("remove list(2)");
}else {
System.out.println(list.get(i));
}
}
}
输出结果:
a
b
remove list(2)
e
什么情况,明明只删除了一个元素,最后输出只有a,b,e
,…
那么再看一组测试,尝试删除list中所有的元素:
public static void forLoopMethod(){
for (int i=0; i < list.size(); i++){
list.remove(i);
}
System.out.println("list size: "+list.size());
System.out.println(list);
}
输出结果:
list size: 2
[b, d]
也是一脸懵逼,明明是删除所有元素…
幸运的是在删除数据的时候没有抛出ConcurrentModificationException
异常,傻了吧,只有使用iterator遍历集合才有可能抛出ConcurrentModificationException
异常,for-Loop
是不会出现这样的情况啦
现在来看下list.remove(index)
这个方法源码:
public E remove(int index) {
//检测是否数组越界
rangeCheck(index);
//修改次数加1
modCount++;
E oldValue = elementData(index);
//计算在删除元素后后面元素要向前移动的步长
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//设置最后一个元素为null,并size减1
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
那么这就不足为怪了,每删除一个元素,数组中后面元素就向前移动一步,就假设我们要删除list中数据c
,它的下标是2,也就是i=2,删除了c
之后,d,e
都从数组后面向前移动了一步,即d
到了c
之后,e
移到了原来d
的位置,但是这时的取到下一个元素的时候,此时i却成了3(因为进行了i++操作),所以取到的值就是elementData[3]
的值,而此时elementData[3]
的值是e
,所以就看到了第一个测试结果了。这也就不难解释当用for-Loop
遍历删除所有数据的时候list.size
的大小为2的结果了,而且输出的结果是呈跳跃式的了
因此,不建议使用for-Loop方式去遍历删除list中的元素,这样的话,数据可能删除不完
public static void iteratorMethod(){
Iterator<String> itr = list.listIterator();
while (itr.hasNext()){
String el = itr.next();
if (el.equals("c")){
itr.remove();
System.out.println("remove c");
}else {
System.out.println(el);
}
}
}
输出结果:
a
b
remove c
d
e
再看一组测试,这个测试案例和上一个不同在于将itr.remove()
换成了list.remove("c")
:
public static void iteratorMethod(){
Iterator<String> itr = list.listIterator();
while (itr.hasNext()){
String el = itr.next();
if (el.equals("c")){
list.remove("c");
System.out.println("remove c");
}else {
System.out.println(el);
}
}
}
输出结果:
a
b
remove c
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
结果很明显,出现了我们期待已久的ConcurrentModificationException
异常
唯一不同的就是将将itr.remove()
换成了list.remove("c")
,而第二个却抛异常了,list.remove(object)
方法和list.remove(index)
方法一样,所以就不用看了,现在看下itr.remove
方法,ArrayList
中Iterator的实现:
private class Itr implements Iterator<E> {
int cursor; // (下一个返回数据的下标)index of next element to return
int lastRet = -1; // (当前返回数据的下标)index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//检测list是否发生了结构化修改
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//cursor变成了下一次返回数据的下标
cursor = i + 1;
//返回当前数据
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
//检测list是否发生了结构化修改
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
这里要知道modCount
就是引起list发生结构化修改(structurally modified)
的次数,比如list发生了add
或者是remove
操作都会引起list发生structurally modified
变化。如果不发生structurally modified
的话,expectedModCount
和modCount
是始终相等的。
在Itr.remove
方法中,调用了外部类ArrayList.this.remove(lastRet)
方法,之前分析过,把modCount
次数加1,但是又设置expectedModCount = modCount
,所有下次调用itr.remove
方法时候expectedModCount
和modCount
还是相等的,所以测试案例调用itr.remove
方法去删除list中数据的时候,并不会抛出ConcurrentModificationException
异常
而在第二个测试中是直接调用list.remove(index)
方法,我们知道list.remove(index)
会将modCount
次数加1,使得下次调用itr.next
方法时会调用checkForComodification
方法来检测list是否发生了structurally modified
变化,此时显然是不相等的,所以抛出了ConcurrentModificationException
异常
public static void forEnhancedMethod(){
for (String str: list){
if (str.equals("c")){
list.remove("c");
System.out.println("remove list(c)");
}else {
System.out.println(str);
}
list.remove(str);
}
}
输出结果:
a
b
remove list(c)
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
我们再看一组测试案例,将删除数据c
换成删除数据d
:
public static void forEnhancedMethod(){
for (String str: list){
if (str.equals("d")){
list.remove("d");
System.out.println("remove list(d)");
}else {
System.out.println(str);
}
}
}
输出结果:
a
b
c
remove list(d)
也是一脸懵逼啊…
先看下forEnhancedMethod
方法反编译之后:
public static void forEnhancedMethod() {
Iterator var0 = list.iterator();
while(var0.hasNext()) {
String str = (String)var0.next();
if (str.equals("d")) {
//这里不同!!
list.remove("d");
System.out.println("remove list(d)");
} else {
System.out.println(str);
}
}
}
可以发现我们增强for
方式最终转成了iterator方式执行,只是删除的时候不是采用itr.remove
方式,所以第一组测试案例会发生ConcurrentModificationException
异常了。
下面就是看看是什么原因导致第二组测试案例结果了这么神奇,再仔细看下,这个数据d
是在list中倒数第二,在遍历的时候我们有:
while(itr.hasNext()) {
//....
}
再看下Itr中的hasNext
和next
方法,hasNext
方法发现cursor != size
时就会终止循环,而在itr.next
的方法执行之后就会将cursor
后移一位。再结合之前要删除数据d
,这是倒数第二个数据,当遍历到数据d
,调用itr.next
方法之后,经过list.remove(object)
后,list中的size减1,即cursor==list.size=4
。当要遍历数据e
时,执行itr.hasNext
时发现cursor==size
返回false
,所以就终止了遍历,没有输出最后数据e
。
for-Loop方式
删除数据时会跳跃的删除,所以不建议使用,但不会抛出ConcurrentModificationException
异常增强-for方式
在执行删除的时候会转成Iterator执行,但实际调用list.remove(object)
方法,会导致ConcurrentModificationException
异常iterator方式
删除数据时,如果调用itr.remove
方法就不会以导致ConcurrentModificationException
异常,但是如果调用list.remove(object)
方法也还是会抛出ConcurrentModificationException
异常