我们都知道,由于 ConcurrentModificationException
,您无法执行以下操作:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
但这显然有时有效,但并非总是如此。下面是一些具体的代码:
public static void main(String[] args) {
Collection<Integer> l = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
l.add(4);
l.add(5);
l.add(6);
}
for (int i : l) {
if (i == 5) {
l.remove(i);
}
}
System.out.println(l);
}
当然,这会导致:
Exception in thread "main" java.util.ConcurrentModificationException
即使多个线程没有这样做。反正。
这个问题的最佳解决方案是什么?如何在不引发此异常的情况下循环从集合中删除项目?
我在这里也使用了任意的 Collection
,不一定是 ArrayList
,因此您不能依赖 get
。
Iterator.remove()
是安全的,您可以这样使用它:
List<String> list = new ArrayList<>();
// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
// Iterator<String> iterator = list.iterator();
// while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if (string.isEmpty()) {
// Remove the current element from the iterator and the list.
iterator.remove();
}
}
请注意,Iterator.remove()
是在迭代期间修改集合的唯一安全方法;如果在迭代过程中以任何其他方式修改了底层集合,则行为未指定。
来源: docs.oracle > The Collection Interface
同样,如果您有 ListIterator
并且想要添加项,则可以使用 ListIterator#add
,出于同样的原因,您可以使用 Iterator#remove
- 它旨在允许它。
在您的情况下,您尝试从列表中删除,但如果在迭代其内容时尝试 put
进入 Map
,则同样的限制适用。
这有效:
Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
if (iter.next() == 5) {
iter.remove();
}
}
我假设由于 foreach 循环是迭代的语法糖,因此使用迭代器无济于事……但它为您提供了 .remove()
功能。
对于 Java 8,您可以使用 the new removeIf
method。应用于您的示例:
Collection<Integer> coll = new ArrayList<>();
//populate
coll.removeIf(i -> i == 5);
removeIf
使用 Iterator
和 while
循环。你可以在 java 8 java.util.Collection.java
看到它
ArrayList
)会覆盖它。您所指的只是默认实现。
equals
,因此不必实现它。 (当然,如果您在测试中使用 equals
,那么它必须按照您想要的方式实现。)
由于问题已经得到解答,即最好的方法是使用迭代器对象的 remove 方法,我将详细介绍引发错误 "java.util.ConcurrentModificationException"
的位置。
每个集合类都有一个私有类,它实现了 Iterator 接口并提供了 next()
、remove()
和 hasNext()
等方法。
next 的代码看起来像这样......
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
这里方法 checkForComodification
实现为
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
因此,如您所见,如果您明确尝试从集合中删除一个元素。它导致 modCount
与 expectedModCount
不同,从而导致异常 ConcurrentModificationException
。
您可以像您提到的那样直接使用迭代器,或者保留第二个集合并将要删除的每个项目添加到新集合中,然后在最后 removeAll 。这允许您以增加内存使用和 cpu 时间为代价继续使用 for-each 循环的类型安全(这不应该是一个大问题,除非您有非常非常大的列表或非常旧的计算机)
public static void main(String[] args)
{
Collection<Integer> l = new ArrayList<Integer>();
Collection<Integer> itemsToRemove = new ArrayList<>();
for (int i=0; i < 10; i++) {
l.add(Integer.of(4));
l.add(Integer.of(5));
l.add(Integer.of(6));
}
for (Integer i : l)
{
if (i.intValue() == 5) {
itemsToRemove.add(i);
}
}
l.removeAll(itemsToRemove);
System.out.println(l);
}
在这种情况下,一个常见的技巧是(曾经?)倒退:
for(int i = l.size() - 1; i >= 0; i --) {
if (l.get(i) == 5) {
l.remove(i);
}
}
也就是说,我很高兴您在 Java 8 中有更好的方法,例如流上的 removeIf
或 filter
。
ArrayList
或类似的收藏。
for(int i = l.size(); i-->0;) {
?
与 Claudius 相同的答案,带有 for 循环:
for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
Object object = it.next();
if (test) {
it.remove();
}
}
使用 Eclipse Collections,在 MutableCollection 上定义的方法 removeIf
将起作用:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
使用 Java 8 Lambda 语法,可以这样写:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
此处需要调用 Predicates.cast()
,因为在 Java 8 的 java.util.Collection
接口上添加了默认的 removeIf
方法。
注意:我是 Eclipse Collections 的提交者。
制作现有列表的副本并迭代新副本。
for (String str : new ArrayList<String>(listOfStr))
{
listOfStr.remove(/* object reference or index */);
}
人们断言无法从 foreach 循环迭代的 Collection 中删除。我只是想指出这在技术上是不正确的并准确地描述了(我知道 OP 的问题是如此先进以至于避免知道这一点)该假设背后的代码:
for (TouchableObj obj : untouchedSet) { // <--- This is where ConcurrentModificationException strikes
if (obj.isTouched()) {
untouchedSet.remove(obj);
touchedSt.add(obj);
break; // this is key to avoiding returning to the foreach
}
}
并不是说您不能从迭代的 Colletion
中删除,而是您一旦这样做就不能继续迭代。因此,上面代码中的 break
。
抱歉,如果这个答案是一个有点专业的用例并且更适合我从这里到达的原始 thread,那个被标记为重复(尽管这个线程看起来更细微)并被锁定。
使用传统的 for 循环
ArrayList<String> myArray = new ArrayList<>();
for (int i = 0; i < myArray.size(); ) {
String text = myArray.get(i);
if (someCondition(text))
myArray.remove(i);
else
i++;
}
i++
后,相同的代码仍然可以工作。
i++
递增不是有条件的 - 我现在明白这就是你在正文中这样做的原因:)
ConcurrentHashMap 或 ConcurrentLinkedQueue 或 ConcurrentSkipListMap 可能是另一种选择,因为它们永远不会抛出任何 ConcurrentModificationException,即使您删除或添加项目也是如此。
java.util.concurrent
包中。该包中的其他一些类似/常见用例类是 CopyOnWriteArrayList
& CopyOnWriteArraySet
[但不限于这些]。
ConcurrentModificationException
,但在 enhanced-for-loop 中使用它们仍然会导致索引问题(即:仍然跳过元素, 或 IndexOutOfBoundsException
...)
另一种方法是使用 arrayList 的副本仅用于迭代:
List<Object> l = ...
List<Object> iterationList = ImmutableList.copyOf(l);
for (Object curr : iterationList) {
if (condition(curr)) {
l.remove(curr);
}
}
i
不是 index
,而是对象。也许称它为 obj
会更合适。
ListIterator
允许您添加或删除列表中的项目。假设您有 Car
个对象的列表:
List<Car> cars = ArrayList<>();
// add cars here...
for (ListIterator<Car> carIterator = cars.listIterator(); carIterator.hasNext(); )
{
if (<some-condition>)
{
carIterator().remove()
}
else if (<some-other-condition>)
{
carIterator().add(aNewCar);
}
}
previous
方法。
我知道这个问题对于 Java 8 来说太老了,但是对于那些使用 Java 8 的人来说,您可以轻松地使用 removeIf():
Collection<Integer> l = new ArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
l.removeIf(i -> i.intValue() == 5);
现在,您可以使用以下代码删除
l.removeIf(current -> current == 5);
Java 并发修改异常
单线程
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iter.next()
if (value == "A") {
list.remove(it.next()); //throws ConcurrentModificationException
}
}
解决方案:迭代器remove()
方法
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iter.next()
if (value == "A") {
it.remove()
}
}
多线程
复制/转换并迭代另一个集合。对于小型收藏
同步[关于]
线程安全集合[关于]
对于上面的问题,我有一个建议。不需要二级名单或任何额外的时间。请找到一个例子,它会以不同的方式做同样的事情。
//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
Object r = list.get(index);
if( state ) {
list.remove(index);
index = 0;
continue;
}
index += 1;
}
这将避免并发异常。
ArrayList
,因此不能依赖 get()
。不过,否则可能是一个好方法。
Collection
- Collection
接口不包括 get
。 (虽然 FWIW List
接口确实包含“get”)。
while
-循环 List
。但是这个答案+1,因为它排在第一位。
for (Integer i : l)
{
if (i.intValue() == 5){
itemsToRemove.add(i);
break;
}
}
如果您跳过内部 iterator.next() 调用,则捕获是从列表中删除元素之后。它仍然有效!虽然我不建议编写这样的代码,但它有助于理解它背后的概念 :-)
干杯!
线程安全集合修改示例:
public class Example {
private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());
public void removeFromQueue() {
synchronized (queue) {
Iterator<String> iterator = queue.iterator();
String string = iterator.next();
if (string.isEmpty()) {
iterator.remove();
}
}
}
}
一种解决方案可能是旋转列表并删除第一个元素以避免 ConcurrentModificationException 或 IndexOutOfBoundsException
int n = list.size();
for(int j=0;j<n;j++){
//you can also put a condition before remove
list.remove(0);
Collections.rotate(list, 1);
}
Collections.rotate(list, -1);
试试这个(删除列表中等于 i
的所有元素):
for (Object i : l) {
if (condition(i)) {
l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
}
}
您可以使用 while 循环。
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<String, String> entry = iterator.next();
if(entry.getKey().equals("test")) {
iterator.remove();
}
}
while
循环,而是通过 Iterator.
删除
我最终得到了这个 ConcurrentModificationException
,同时使用 stream().map()
方法迭代列表。但是,for(:)
在迭代和修改列表时没有引发异常。
这是代码片段,如果它对任何人都有帮助:这里我正在迭代 ArrayList<BuildEntity>
,并使用 list.remove(obj) 对其进行修改
for(BuildEntity build : uniqueBuildEntities){
if(build!=null){
if(isBuildCrashedWithErrors(build)){
log.info("The following build crashed with errors , will not be persisted -> \n{}"
,build.getBuildUrl());
uniqueBuildEntities.remove(build);
if (uniqueBuildEntities.isEmpty()) return EMPTY_LIST;
}
}
}
if(uniqueBuildEntities.size()>0) {
dbEntries.addAll(uniqueBuildEntities);
}
如果使用 HashMap,在较新版本的 Java (8+) 中,您可以选择 3 个选项中的每一个:
public class UserProfileEntity {
private String Code;
private String mobileNumber;
private LocalDateTime inputDT;
// getters and setters here
}
HashMap<String, UserProfileEntity> upMap = new HashMap<>();
// remove by value
upMap.values().removeIf(value -> !value.getCode().contains("0005"));
// remove by key
upMap.keySet().removeIf(key -> key.contentEquals("testUser"));
// remove by entry / key + value
upMap.entrySet().removeIf(entry -> (entry.getKey().endsWith("admin") || entry.getValue().getInputDT().isBefore(LocalDateTime.now().minusMinutes(3)));
最好的方法(推荐)是使用 java.util.concurrent
包。通过使用此包,您可以轻松避免此异常。参考修改代码:
public static void main(String[] args) {
Collection<Integer> l = new CopyOnWriteArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l) {
if (i.intValue() == 5) {
l.remove(i);
}
}
System.out.println(l);
}
如果 ArrayList:remove(int index)- if(index is last element's position) 它避免没有 System.arraycopy()
并且不需要时间。
如果(索引减少),数组复制时间会增加,顺便说一下列表的元素也会减少!
最有效的移除方式是 - 按降序移除其元素:while(list.size()>0)list.remove(list.size()-1);
//takes O(1) while(list.size()>0)list.remove(0);
//takes O(factorial(n))
//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
Integer integer = rdm.nextInt();
ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion
// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++)
if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion
// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--)
if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion
// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
索引循环:1090 毫秒
对于 desc 索引:519 毫秒---最好的
迭代器:1043 毫秒
我知道这个问题只假设一个 Collection
,而不是更具体地说是任何 List
。但对于那些阅读此问题并确实使用 List
参考的人,如果您想避免使用 Iterator
,则可以使用 while
循环(同时在其中修改)避免 ConcurrentModificationException
strong> (如果您想在一般情况下避免它,或者专门避免它以实现不同于在每个元素处从头到尾停止的循环顺序[我相信这是 Iterator
本身可以做的唯一顺序]):
*更新:请参阅下面的评论,阐明类似的情况也可以通过传统的 for-loop 实现。
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 1;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i++);
} else {
i += 2;
}
}
该代码没有 ConcurrentModificationException。
在那里我们看到循环不是从一开始就开始,也不是在 每个 元素处停止(我相信 Iterator
本身不能这样做)。
FWIW 我们还看到 get
在 list
上被调用,如果它的引用只是 Collection
(而不是更具体的 List
类型的 Collection
)则无法完成 - List
接口包括 { 1},但 Collection
接口没有。如果不是因为这种差异,那么 list
引用可以改为 Collection
[因此从技术上讲,此答案将是直接答案,而不是切线答案]。
FWIWW 相同的代码在修改为从每个元素的开始到停止后仍然有效(就像 Iterator
顺序):
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 0;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i);
} else {
++i;
}
}
ConcurrentModificationException
,但 not traditional-for-loop (另一个答案使用) - 没有意识到之前是我有动力写这个答案的原因(我当时错误地认为是 all for-loops 会抛出异常)。
你也可以使用递归
java中的递归是一个方法不断调用自身的过程。 java中调用自身的方法称为递归方法。
这可能不是最好的方法,但对于大多数小情况,这应该是可以接受的:
“创建第二个空数组并仅添加您想要保留的那些”
我不记得我是从哪里读到这篇文章的……为了公正起见,我会制作这个维基,希望有人能找到它,或者只是为了不赢得我不应该得到的代表。
不定期副业成功案例分享
iterator.next()
调用放在 for 循环中吗?如果没有,有人可以解释为什么吗?List.add
在同样的意义上也是“可选的”,但您不会说添加到列表中是“不安全的”。