概述
最近在看阿里巴巴java开发手册,其中有一条是:不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
之前自己也有了解过其中的原因,已经比较模糊,所以在这里重新看一次并记录一下。
在for中直接remove元素分析
测试代码
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("8");
list.add("4");
list.add("2");
list.add("6");
for (String item : list){
if ("8".equals(item)){
list.remove(item);
}
System.out.print(item + " ");
}
}
1 2 8 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 main.com.cjl.csdn.RemoveUseIterator.main(RemoveUseIterator.java:14)
上面使用的是foreach的循环方式,下面我们再使用普通for循环的方式。
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("8");
list.add("4");
list.add("2");
list.add("6");
for (int i = 0; i < list.size(); i++){
if ("8".equals(list.get(i))){
list.remove(list.get(i));
}
System.out.print(list.get(i) + " ");
}
}
1 2 4 2 6
Process finished with exit code 0
由上面的两个例子我们可以猜想一下,for与foreach遍历集合时,调用方式应该是不一样的,同样是遍历为什么一个抛出异常一个没有抛出异常。所以我就对两个class文件分别进行了反编译看下他们的区别;
String item;
for(Iterator var2 = list.iterator(); var2.hasNext(); System.out.print(item + " ")) {
item = (String)var2.next();
if ("8".equals(item)) {
list.remove(item);
}
}
果然,上面这种是foreach的反编译结果中的一部分,而使用普通for循环方式的反编译结果与原来一致。
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;
}
通过调用我们可以看到,for循环与foreach循环都是直接调用到上面的这个方法的。上面的方法中的modCount++是需要注意的,这也是为什么最后会报错的一个原因。remove之后,我们将继续遍历下一个元素,由于遍历的方式不同,所以调用的方法也是不同的。
public boolean hasNext() {
return cursor != SubList.this.size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= SubList.this.size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[offset + (lastRet = i)];
}
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
上面是foreach调用的方式,首先会去hasNext中判断是否还有下一个元素,然后去调用next方法,可以看到第一行就是去调用checkForComodification方法,该方法会去判断modCound与expectedModCount是否相等,当他们不相等的时候就会报出我们上面出现的错误。还记得在调用remove的时候我们只对modCount变量加1,并没有对expectedModCount进行改变。
使用普通的for方式并不会走到这些方法中,所以也没有对modCount,expectedModCount进行校验。下面我们来看下使用iterator的方式。
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("8");
list.add("4");
list.add("2");
list.add("6");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String str = iterator.next();
System.out.print(str + " ");
if ("8".equals(str)){
iterator.remove();
}
}
}
执行上面的的代码是能够正确执行的,我们可以看到删除元素是使用的iterator中的remove方法,跟for使用的remove方法有什么不一样呢。
private class Itr implements Iterator<E>
Itr是ArrayList是中的内部类,iterator调用的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();
}
}
可以看到其中的remove方法也会去调用到ArrayList中的方法,只是在那基础之上再封装了一层,具体内容可以看到
expectedModCount = modCount;
这句,将expectedModCount赋值与modCount一样。
在遍历下一个元素的时候会去调用到与foreach一样的方法;
public E next() {
checkForComodification();
int i = cursor;
if (i >= SubList.this.size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[offset + (lastRet = i)];
}
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
进行expectedModCount与modCount的判断,此时两个值是一致的,所以能够进行删除。
总结:
1、foreach方式的remove直接调用的ArrayList中的remove方法,修改了modCount的值,并没有修改expectedModCount的值,导致调用iterator的next方法去判断两个值时,报错。
2、for循环方法并未涉及到比较modCount与expectedModCount的值,所以能够进行删除。
3、iterator方式的remove调用的iterator中的remove方法,对expectedModCount也进行了一个修改,所以在调用iterator的next方法这两个值是相同的,所以能够执行成功。
最后
以上就是现实盼望为你收集整理的循环中使用Iterator删除元素的全部内容,希望文章能够帮你解决循环中使用Iterator删除元素所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复