如何在Java中处理ConcurrentModificationException? 在循环中从ArrayList中删除元素时要当心...

从Java中从ArrayList中删除元素时常见的问题之一是ConcurrentModificationException。 如果您对索引使用经典的for循环或增强的for循环,并尝试使用remove()方法从ArrayList中remove()元素,则将获得C oncurrentModificationException但如果使用Iterator的remove方法或ListIterator的
remove()方法,那么您将不会遇到此错误,并且能够删除该元素。 这是Java中的一条不成文的规则,在遍历列表时,在集合支持故障安全迭代器(例如CopyOnWriteArrayList remove()之前,不应该add()remove()元素,该迭代器对列表的副本而不是原始列表进行操作。

出现此错误的主要问题是,它使开发人员混淆了列表正在被多个线程修改,这就是Java抛出此错误的原因,这不是事实。 大多数时候
即使没有多个线程修改列表,也会出现ConcurrentModificationException

这是用词不当,请不要为此而上当。 尽管似乎自然而然地认为其他线程可能试图同时修改集合,但这通常违反了Java规则。

在本文中,我将解释此错误,并且我们将提供许多代码示例,即使使用单线程也可以重现此代码,并了解如何在Java中修改ArrayList时如何避免并发修改错误。

顺便说一句,如果您不熟悉集合类(例如ArrayList本身),那么您应该参加在线课程,例如
Java基础知识:学习在Udemy上编写正确的代码是一个不错的起点。

单线程中的ConcurrentModificationException

这是在Java中再现并发修改异常的第一个示例。 在此程序中,我们使用增强的foreach循环遍历ArrayList并删除选择性元素,例如,使用ArrayList的remove方法删除匹配特定条件的元素。

例如,在下面的代码中,我们首先添加了几本不错的编程书,例如, Programming PearlsClean CodeCode Complete到ArrayList中,然后删除标题中带有Code的任何元素。

package beginner;

import java.util.ArrayList;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       // Using forEach loop to iterate and removing 
       // element during iteration will throw 
       // ConcurrentModificationException in Java
       for(String book: listOfBooks){
           if(book.contains("Code"));{
               listOfBooks.remove(book);
           }
       }
   }

}
Output
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at beginner.HelloWorldApp.main(HelloWorldApp.java:18)

您可以看到即使我们只有一个线程,即与ArrayList一起运行的主线程,也会出现此错误。 之所以会出现ConcurrentModification错误,是因为我们没有使用Iterator,而是仅调用listOfBooks.remove()方法。

在这段代码中,我使用的Java 1.5增强的for循环,你必须知道如何for循环增强 Java中的作品。

for循环和增强的for循环之间的区别在于,以后内部使用Iterator遍历集合的所有元素。 有关更深入的讨论,请参见此处

使用Classical for循环和ArrayList.remove(index)

这是从ArrayList中删除元素的另一个有趣的代码示例。 令人惊讶的是,当您第一次运行此代码时,它不会引发ConcurrentModificationException吗? 你知道为什么吗?

好吧,在查看代码后的解释之前,请尝试一下。 确实是有关Java编程语言和Collection框架的此类次要细节,这将使您成为一名优秀的开发人员,并且如果您正在为此做好准备,还将帮助您获得Java认证

package beginner;

import java.util.ArrayList;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       System.out.println("List before : " + listOfBooks);
       for(int i=0; i<listOfBooks.size(); i++){
           String book = listOfBooks.get(i);
           if(book.contains("Programming")){
               System.out.println("Removing " + book);
               listOfBooks.remove(i); // will throw CME
           }
       }
       System.out.println("List after : " + listOfBooks);
   }

}

Output
List before : [Programming Pearls, Clean Code, Effective Java, Code Complete]
Removing Programming Pearls
List after : [Clean Code, Effective Java, Code Complete]

这段代码不会引发ConcurrentModificationException因为在这里我们没有使用 Iterator,而是在使用传统的for循环。

是Iterator引发ConcurrentModificationException ,而不是ArrayList的remove方法,因此在下面的代码中看不到该错误。

如果查看ArrayList.java的代码,您会注意到有一个实现Iterator接口的嵌套类,并且next()方法调用checkForComodification()函数,该函数实际上检查ArrayList在迭代过程中是否已修改,如果modCount没有, “T配以expectedModCount则抛出ConcurrentModificationException

final void checkForComodification() {
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

此类问题在Oracle Java认证中也很受欢迎,例如OCAJP( 1z0-808 )和OCPJP( 1Z0-809 ),因此,如果您准备参加这些考试,则应该知道答案。

这是ArrayList.java类的完整代码片段,供您快速参考:

使用迭代器,但使用ArrayList的remove方法

现在,让我们看另一个代码示例,其中Java程序员认为他已正确完成了所有工作,但仍收到并发修改异常。 您能发现错误吗? 这确实很常见,我在Java论坛,StackOverflow和要求解决此问题的Facebook Java组上经常看到这种代码。

package beginner;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       Iterator<String> iterator = listOfBooks.iterator();
       while(iterator.hasNext()){
           String book = iterator.next();
           listOfBooks.remove(book);
       }
   }

}

Output
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at beginner.HelloWorldApp.main(HelloWorldApp.java:18)

这段代码的真正问题在于,即使代码使用Iterator遍历ArrayList ,也并不是真正使用Iterator.remove()方法删除该元素。 它只是使用Iterator获取下一个元素,但调用ArrayList.remove()方法删除该元素。

我知道,当您知道原因时就很容易了,但实际上,很多时候程序员甚至要花几个小时才能弄清楚问题出在哪里。 因此,请当心。

顺便说一句,如果您正在学习Java,那么我建议加入Complete Java Masterclass,以更好地学习Java并避免此类常见错误。

删除元素的正确方法是使用Iterator的remove方法

最后,这是在迭代过程中从ArrayList删除元素的正确方法。 在此示例中,我们使用Iterator进行了迭代以及删除了元素。 该代码还可以,但是有一个严重的限制,您只能使用此代码删除当前元素。 您不能从Java中的ArrayList中删除任何任意元素。

package beginner;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       System.out.println("List before : " + listOfBooks);
       Iterator<String> iterator = listOfBooks.iterator();
       while(iterator.hasNext()){
           String book = iterator.next();
           System.out.println("Removing " + book);
           iterator.remove();
       }
       System.out.println("List after : " + listOfBooks);
   }

}
Output
List before : [Programming Pearls, Clean Code, Effective Java, Code Complete]
Removing Programming Pearls
Removing Clean Code
Removing Effective Java
Removing Code Complete
List after : []

同样的行为也适用于ListIterator 。 我的意思是您可以用ListIterator替换Iterator,并且代码可以正常工作。 ListIterator还允许您在两个方向上进行导航,即向前和向后导航。

这就是在迭代过程中如何从ArrayList中删除元素时如何避免ConcurrentModificationException的全部内容。 您可以使用相同的技术来避免ConcurrentModificationException同时从具有快速失败的Iterator的任何其他集合类中删除元素,例如LinkedList。 顺便说一句,如果您不熟悉Java编程,那么可以参加一门很好的综合课程,例如Java Basics:学习在Udemy上编写正确的方法 ,可以帮助您更好更快地学习Java。

您可能喜欢的其他Java故障排除指南

如何在Java中解决ArrayIndexOutOfBoundsException? ( 指南
如何在Java中解决NullPointerException? (指南)
如何解决“系统找不到指定的路径”错误? ( 解决方案 ) 从命令行运行Java程序时如何解决NoClassDefFoundError? ( 解决方案 ) 如何解决Android Studio中的“找不到JVM,请安装64位JDK”错误? ( 解决方案 ) 如何处理JDBC和MySQL中SQLException“找不到合适的驱动程序”错误? ( 指南 ) 如何解决Java中的NumberFormatException? ( 指南 ) 如何解决Minecraft – java.lang.UnsatisfiedLinkError:lwjgl64.dll:访问被拒绝? ( 解决方案 ) 如何在Java中修复java.lang.ArrayIndexOutOfBoundsException:1? ( 解决方案 ) 如何修复java.net.SocketException:软件导致连接中止:recv失败( 修复

感谢您阅读本教程,如果您喜欢本教程,请与您的朋友和同事分享。 如果您有任何问题或建议,请发表评论。

翻译自: https://www.javacodegeeks.com/2018/01/deal-concurrentmodificationexception-java-beware-removing-elements-arraylist-loop.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值