从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 Pearls , Clean Code , Code 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 Javafor(String book: listOfBooks){if(book.contains("Code"));{listOfBooks.remove(book);}}}}
Output
Exception in thread "main" java.util.ConcurrentModificationExceptionat 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.ConcurrentModificationExceptionat 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