为什么For循环操作List会报错

为什么For循环操作List会报错?

分析问题

这是今天下午突然想到的一个问题,闲来无事,探探究竟!

先来看看Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void main(String[] args) {

List<String> list = new ArrayList();
list.add("java01");
list.add("java02");
list.add("java03");

//正常
Iterator iterator=list.iterator();
while(iterator.hasNext()){
if("java01".equals(iterator.next())){
iterator.remove();
}
}

//异常 java.util.ConcurrentModificationException
for (String str:list){
if("java01".equals(str)){
list.remove(1);
}
}


//异常 java.util.ConcurrentModificationException
for (String str:list){
if("java01".equals(str)){
list.add("java04");
}
}
System.out.println();
}

在使用Iterator去遍历这个List的时候就不会报错,使用For循环去直接操作List就会抛出java.util.ConcurrentModificationException这个错误。先来看看为什么会有这个错误。

1
2
3
4
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)
at com.mybatis.example.main.main(main.java:38)

这个是异常,可以看到在ArrayList的909行抛出异常了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
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];
}

是在这个checkForComodification方法里面检查两个属性是否相等,如果不相等就抛出异常。可是这里有个问题,为什么在用For的时候会去调用Iterator的next方法呢。

分析源码

看看编译的Class文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("java01");
list.add("java02");
list.add("java03");
Iterator iterator = list.iterator();

//正常
while(iterator.hasNext()) {
if ("java01".equals(iterator.next())) {
iterator.remove();
}
}

Iterator var3 = list.iterator();
//异常
String str;
while(var3.hasNext()) {
str = (String)var3.next();
if ("java01".equals(str)) {
list.remove(1);
}
}

var3 = list.iterator();
//异常
while(var3.hasNext()) {
str = (String)var3.next();
if ("java01".equals(str)) {
list.add("java04");
}
}

System.out.println();
}

这里可以看到,我写的For循环,在编译过后变成了上述代码那样,也是使用的Iterator,至于为什么会编译成这样就不管了,来看看在哪里报的错

Iterator下面的remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
//检查modCount和expectedModCount是否相等
checkForComodification();

try {
//删除掉节点
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//将当前List修改的结构次数modCount赋值给预期的修改次数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

这个remove里面有一个赋值的过程,就是将List修改的次数modCount这个值给预期的修改次数expectedModCount,保证了两个值的相等

ArrayList下的remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  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);
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

其实看到这,就不用往后看了,因为在ArrayList下面的remove只是更改了modCount这个值,并没有将这个值赋值给expectedModCount,所以在下次去next的时候,去调用checkForComodification()这个方法的时候,发现modCount和expectedModCount不一致,就会抛出异常。

总结

其实很简单的一个问题,做个记录,这里是为了安全考虑,保证了List同时只会有一个线程去更改他的结果

-------------本文结束感谢您的阅读-------------
0%