我试图从数组列表中删除一些元素,同时像这样迭代它:
for (String str : myArrayList) {
if (someCondition) {
myArrayList.remove(str);
}
}
当然,当我在迭代myArrayList的同时试图从列表中删除项时,我得到了一个ConcurrentModificationException。有没有简单的办法来解决这个问题?
我试图从数组列表中删除一些元素,同时像这样迭代它:
for (String str : myArrayList) {
if (someCondition) {
myArrayList.remove(str);
}
}
当然,当我在迭代myArrayList的同时试图从列表中删除项时,我得到了一个ConcurrentModificationException。有没有简单的办法来解决这个问题?
当前回答
不,不,不!
在单个受到威胁的任务中,您不需要使用Iterator,而且,CopyOnWriteArrayList(由于性能损失)。
解决方案要简单得多:尝试使用规范的for循环而不是for-each循环。
根据Java版权所有者(几年前是Sun,现在是Oracle)的说法,对于每个循环指南,它使用迭代器遍历集合,只是隐藏它以使代码看起来更好。但不幸的是,正如我们所看到的,它带来的问题比利润更多,否则这个话题就不会出现了。
例如,当进入修改后的ArrayList的下一次迭代时,这段代码将导致java.util.ConcurrentModificationException:
// process collection
for (SomeClass currElement: testList) {
SomeClass founDuplicate = findDuplicates(currElement);
if (founDuplicate != null) {
uniqueTestList.add(founDuplicate);
testList.remove(testList.indexOf(currElement));
}
}
但是下面的代码工作得很好:
// process collection
for (int i = 0; i < testList.size(); i++) {
SomeClass currElement = testList.get(i);
SomeClass founDuplicate = findDuplicates(currElement);
if (founDuplicate != null) {
uniqueTestList.add(founDuplicate);
testList.remove(testList.indexOf(currElement));
i--; //to avoid skipping of shifted element
}
}
因此,尽量使用索引方法迭代集合,避免for-each循环,因为它们是不相等的! For-each循环使用一些内部迭代器,检查集合修改并抛出ConcurrentModificationException异常。要确认这一点,请在使用我发布的第一个示例时仔细查看打印的堆栈跟踪:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at TestFail.main(TestFail.java:43)
对于多线程使用相应的多任务方法(如synchronized关键字)。
其他回答
虽然其他建议的解决方案工作,如果你真的希望解决方案是线程安全的,你应该用CopyOnWriteArrayList替换ArrayList
//List<String> s = new ArrayList<>(); //Will throw exception
List<String> s = new CopyOnWriteArrayList<>();
s.add("B");
Iterator<String> it = s.iterator();
s.add("A");
//Below removes only "B" from List
while (it.hasNext()) {
s.remove(it.next());
}
System.out.println(s);
作为对其他人答案的替代,我总是这样做:
List<String> toRemove = new ArrayList<String>();
for (String str : myArrayList) {
if (someCondition) {
toRemove.add(str);
}
}
myArrayList.removeAll(toRemove);
这将避免您必须直接处理迭代器,但需要另一个列表。不管出于什么原因,我一直更喜欢这条路线。
不,不,不!
在单个受到威胁的任务中,您不需要使用Iterator,而且,CopyOnWriteArrayList(由于性能损失)。
解决方案要简单得多:尝试使用规范的for循环而不是for-each循环。
根据Java版权所有者(几年前是Sun,现在是Oracle)的说法,对于每个循环指南,它使用迭代器遍历集合,只是隐藏它以使代码看起来更好。但不幸的是,正如我们所看到的,它带来的问题比利润更多,否则这个话题就不会出现了。
例如,当进入修改后的ArrayList的下一次迭代时,这段代码将导致java.util.ConcurrentModificationException:
// process collection
for (SomeClass currElement: testList) {
SomeClass founDuplicate = findDuplicates(currElement);
if (founDuplicate != null) {
uniqueTestList.add(founDuplicate);
testList.remove(testList.indexOf(currElement));
}
}
但是下面的代码工作得很好:
// process collection
for (int i = 0; i < testList.size(); i++) {
SomeClass currElement = testList.get(i);
SomeClass founDuplicate = findDuplicates(currElement);
if (founDuplicate != null) {
uniqueTestList.add(founDuplicate);
testList.remove(testList.indexOf(currElement));
i--; //to avoid skipping of shifted element
}
}
因此,尽量使用索引方法迭代集合,避免for-each循环,因为它们是不相等的! For-each循环使用一些内部迭代器,检查集合修改并抛出ConcurrentModificationException异常。要确认这一点,请在使用我发布的第一个示例时仔细查看打印的堆栈跟踪:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at TestFail.main(TestFail.java:43)
对于多线程使用相应的多任务方法(如synchronized关键字)。
可以使用迭代器remove()函数从底层集合对象中删除对象。但在这种情况下,您可以从列表中删除同一对象,而不能删除任何其他对象。
从这里
使用迭代器并调用remove():
Iterator<String> iter = myArrayList.iterator();
while (iter.hasNext()) {
String str = iter.next();
if (someCondition)
iter.remove();
}