[英]Peculiar behavior of ArrayList remove() - Why?
当我们删除-1
并清空ArrayList
它会抛出ConcurrentModificationException
,当我们从同一个空ArrayList
删除0
,它会抛出NoSuchElementException
。
请找到以下代码:
public class Test {
public static void main(String[] argv) {
ArrayList<Integer> list = new ArrayList<Integer>();
Iterator<Integer> it = list.iterator();
try {
list.remove(-1);
} catch (IndexOutOfBoundsException e) {
}
try {
it.next();// Throwing ConcurrentModificationException
} catch (ConcurrentModificationException e) {
System.err.println("ConcurrentModificationException 1");
} catch (NoSuchElementException e) {
System.err.println("NoSuchElementException 1 ");
}
list = new ArrayList<Integer>();
it = list.iterator();
try {
list.remove(0);
} catch (IndexOutOfBoundsException e) {
}
try {
it.next();// Throwing NoSuchElementException
} catch (NoSuchElementException e) {
System.err.println("NoSuchElementException 2");
} catch (ConcurrentModificationException e) {
System.err.println("ConcurrentModificationException 2 ");
}
}
}
从我的理解NoSuchElementException
是好的,但为什么抛出ConcurrentModificationException
?
如果检查ArrayList的代码。 首先执行范围检查,然后添加修改计数。
rangeCheck(index);
modCount++;
在范围检查方法中,范围检查仅适用于正数。
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
因此remove(0)
不会添加mod计数,但remove(-1)
会添加。 modCount导致迭代器抛出ConcurrentModificationException。
这是(IMO)一个错误,虽然是一个非常小的错误。
代码看起来像这样(在Java 8中):
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
rangeCheck
调用检查index < size
,但不rangeCheck
index >= 0
。 据推测,这是为了避免冗余检查...因为elementData
调用执行了一个不可优化的数组边界检查来处理负索引情况。
但问题是modCount++
是在数组边界检查之前发生的。 哎呀!
一个潜在的解决方法是在elementData
调用之后移动modCount++
,但这可能不正确。 这个交易是modCount
是一个volatile
,因此读/写它对访问下一个发生的elementData
有一个隐含的同步效果。
如果你举报它们会修复它吗? 可能不是!
修复它有可能破坏“工作”的代码,如果你按照我上面建议的方式这样做。 不可否认,你破坏的代码无论如何都是错误的,因为它依赖于因volatile
而偶然发生的同步。 即便如此,代码破灭的人也会尖叫 。 (尤其是因为这将是一个难以诊断的破损。)
另外,如果你添加一个明确的size >= 0
检查,你最有可能做的工作代码可测量慢。
“bug”只会影响不正确的代码。 您不应该使用迭代同时调用ArrayList::remove
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.