在分析前我们先做几个测试
public class TestList {
public static void main(String[]args){
List<String> list = new ArrayList<String>();
list.add("1");list.add("2");
list.add("3");list.add("4");
TestList.testFor(list);
TestList.testForeach(list);
TestList.testIteratorForListRemove(list);
TestList.testIteratorForIteratorRemove(list);
}
//测试For循环
public static void testFor(List<String> list){
try{
for(int i=0;i<list.size();i++){
System.out.println("长度:"+list.size());
if(list.contains("1")){
list.remove(i);
}
System.out.println("长度:"+list.size());
System.out.println("-----------------------------");
}
}catch(ConcurrentModificationException e){
System.out.println("出现ConcurrentModificationException异常");
}
}
//测试Foreach循环
public static void testForeach(List<String> list){
try{
for(String str:list){
System.out.println("长度:"+list.size());
if(list.contains("1")){
list.remove(str);
}
System.out.println("长度:"+list.size());
System.out.println("-----------------------------");
}
}catch(ConcurrentModificationException e){
System.out.println("出现ConcurrentModificationException异常");
}
}
//测试迭代器使用List的remove方法
public static void testIteratorForListRemove(List<String> list){
try{
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println("长度:"+list.size());
String str = it.next();
if(list.contains("1")){
list.remove(str);
}
System.out.println("长度:"+list.size());
System.out.println("-----------------------------");
}
}catch(ConcurrentModificationException e){
System.out.println("出现ConcurrentModificationException异常");
}
}
//测试迭代器使用迭代器的remove方法
public static void testIteratorForIteratorRemove(List<String> list){
try{
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println("长度:"+list.size());
String str = it.next();
if(list.contains("1")){
it.remove();
}
System.out.println("长度:"+list.size());
System.out.println("-----------------------------");
}
}catch(ConcurrentModificationException e){
System.out.println("出现ConcurrentModificationException异常");
}
}
}
结果展示
//For循环
长度:4
长度:3
-----------------------------
长度:3
长度:3
-----------------------------
长度:3
长度:3
-----------------------------
//Foreach循环(捕获异常)
长度:4
长度:3
-----------------------------
出现ConcurrentModificationException异常
//迭代器List的remove(捕获异常)
长度:4
长度:3
-----------------------------
长度:3
出现ConcurrentModificationException异常
//迭代器的remove
长度:4
长度:3
-----------------------------
长度:3
长度:3
-----------------------------
长度:3
长度:3
-----------------------------
长度:3
长度:3
-----------------------------
通过结果显示我们能很清楚的知道使用Foreach和使用迭代器且使用List自身的remove方法会抛出ConcurrentModificationException异常。
那么为什么会是这样的结果呢?首先先分析这四个遍历的情况,第一个For循环是最基础的循环,其他三个都是使用了迭代器进行循环,而抛出的异常的都是使用了迭代器的循环,由此我们可以推断产生异常的原因和迭代器内部机制有很大的关系,这就好办了,现在我们来看看Iterator迭代器中的源码情况,在这里注意一下expectedModCount、modCount这两个变量,抛出异常就是它们两个不相等,下面详细讲解为什么会不相等。
//源码
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() {
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];
}
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
通过上面的测试示例我们可以很清楚的知道会用到源码中的hashNext、next、remove等方法,先分析Foreach循环,Foreach循环的原理是默认会调用迭代器中的hashNext和next方法,首先先用hashNext方法判断是否还有下一个元素,如果有,就调用next方法,获取这个元素并赋值给冒号前面的变量,如此重复直至hashNext为fasle的时候就会结束循环。因此Foreach循环会调用迭代器中的两个方法hashNext和next,hashNext方法我们看源码没有什么毛病,重点是在next方法。
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 void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
首先看第一行它会调用checkForComodification方法进行检查modCount和expectedModCount是否相等,不相等就会抛出异常。这时候你会有疑问了(在源码中它们是相等的啊)?
int expectedModCount = modCount;
没错在迭代器中它们是相等的,但是你调用了list.remove(index)方法,注意看下面代码remove方法的第二行,modCount++;自增了,当list调用了remove方法后Foreach循环会调用迭代器中的next方法,next方法会调用checkForComodification方法进行判断所以就会抛出ConcurrentModificationException异常。
//ArrayList中的remove方法
public E remove(int index) {
rangeCheck(index);
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;
}
那么为什么第四个方法也就是使用了迭代器本身的remove方法不会报错呢?且看源码
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();
}
}
迭代器中的已经对这两个变量做了处理让它们始终是相等的。
总结
1、尽量不要在遍历的时候修改集合中的元素
2、如果硬要修改就使用迭代器本身自带的修改元素的方法
3、哈哈,选择for循环是最稳的