今天修改一个bug,需要取一个List和一个Set的交集,使用了双重循环。想着提高循环效率,每加入一个交集中的元素,就将List中的元素删除,减少不必要的循环。结果直接调用了List的remove()方法,抛出了java.util.ConcurrentModificationException异常。这时才忽然记起之前看过的List循环中使用remove()方法要特别注意,尤其是forEach的循环。

不使用forEach的循环

  使用常规的for循环写法,代码如下:


public static void main(String [] args){
List<String> list = new ArrayList<String>();
list.add("111");
list.add("111");
list.add("111");
list.add("333");
list.add("333"); for (int i = 0; i < list.size(); i++){
if ("111".equals(list.get(i))){
list.remove("111");
// i--;不加这句会少删除一个111
}
System.out.println(list.size());
}
}

  先说说list的remove()方法,该方法有两个,一个是remove(Object obj),另一个是remove(int index)。根据参数很容易理解,而这里要说的是remove(obj)会删除list中的第一个该元素,remove(index)会删除该下标的元素。调用一次remove方法,会使list中的所有该删除元素后面的元素前移。看看一个remove(Object obj)的源码:


public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

  remove是可以删除null的,但一般情况都是走else路径,再看看faseRemove方法:


private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // Let gc do its work
}大专栏  List.remove()的使用注意>

  System.arraycopy就是将list中删除元素的后面迁移,暂时没继续深入了解。总得来说,这样的操作是因为没有对变化的list做出相应的下标处理而产生了错误的结果,但是并没有产生异常。

使用forEach循环

  错误的使用代码如下:


public static void main(String [] args){
List<String> list = new ArrayList<String>();
list.add("111");
list.add("111");
list.add("111");
list.add("333");
list.add("333");
for (String str : list){
if("111".equals(str)){
list.remove(str);//抛出异常java.util.ConcurrentModificationException
}
}
}

  这样使用会抛出java.util.ConcurrentModificationException。在forEach中,遍历的集合都必须实现Iterable接口(数组除外)。而forEach的写法实际是对Iterator遍历的简写,类似于以下代码:


public void display(){
for(String s : strings){
System.out.println(s);
} Iterator<String> iterator = strings.iterator();
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}

  上面的forEach和下面的Iterator迭代器效果一样,涉及到编译原理的一些内容,就不深究了。可以理解为forEach将其中的集合转为了迭代器进行遍历,而该迭代器内部有一个next()方法,代码如下:


public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

  可以看到在使用next()方法获取下一个元素的之前会先检查迭代器修改次数,在我们使用ArrayList的remove()方法删除元素时,实际只修改了modCount,这样就会造成modCount和expectedModCount不相等,从而抛出异常。而使用迭代器本身的remove()方法则不会,因为Iterator本身的remove()方法会同时修改modCount和expectedModCount。

  引用一段网上的解释:

  Iterator是工作在一个独立的线程中,并且拥有一个mutex锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照fail-fast原则Iterator会马上抛出java.util.ConcurrentModificationException异常。所以Iterator在工作的时候是不允许被迭代的对象被改变的。但你可以使用Iterator本身的方法remove()来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

  最后总结一下就是,forEach将List转为了Iterator,删除元素就需要使用Iterator的remove()方法,错误地使用了List.remove()方法就会抛出异常。

参考

  Java中ArrayList循环遍历并删除元素的陷阱

  List集合remove元素的问题

  Java语法糖1:可变长度参数以及foreach循环原理

  java forEach实现原理

List.remove()的使用注意的更多相关文章

  1. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  2. [LeetCode] Remove K Digits 去掉K位数字

    Given a non-negative integer num represented as a string, remove k digits from the number so that th ...

  3. [LeetCode] Remove Duplicate Letters 移除重复字母

    Given a string which contains only lowercase letters, remove duplicate letters so that every letter ...

  4. [LeetCode] Remove Invalid Parentheses 移除非法括号

    Remove the minimum number of invalid parentheses in order to make the input string valid. Return all ...

  5. [LeetCode] Remove Linked List Elements 移除链表元素

    Remove all elements from a linked list of integers that have value val. Example Given: 1 --> 2 -- ...

  6. [LeetCode] Remove Duplicates from Sorted List 移除有序链表中的重复项

    Given a sorted linked list, delete all duplicates such that each element appear only once. For examp ...

  7. [LeetCode] Remove Duplicates from Sorted List II 移除有序链表中的重复项之二

    Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numb ...

  8. [LeetCode] Remove Duplicates from Sorted Array II 有序数组中去除重复项之二

    Follow up for "Remove Duplicates":What if duplicates are allowed at most twice? For exampl ...

  9. [LeetCode] Remove Element 移除元素

    Given an array and a value, remove all instances of that value in place and return the new length. T ...

  10. [LeetCode] Remove Duplicates from Sorted Array 有序数组中去除重复项

    Given a sorted array, remove the duplicates in place such that each element appear only once and ret ...

随机推荐

  1. 吴裕雄--天生自然 JAVA开发学习:数据结构

    import java.util.Vector; import java.util.Enumeration; public class EnumerationTester { public stati ...

  2. Git与IDEA集成

    软件配置: 系统版本:Windows10 JDK版本:1.8 Git版本:2.19.1 IDEA版本:2016.3 Maven版本:3.5.4 Git安装: Git下载地址:https://git-s ...

  3. linux中常见压缩文件格式

    文件后缀名 说明 *.zip zip 程序打包压缩的文件 *.rar rar 程序压缩的文件 *.7z 7zip 程序压缩的文件 *.tar tar 程序打包,未压缩的文件 *.gz gzip 程序( ...

  4. 使用 try-with-resources 优雅关闭资源

    桂林SEO:我们知道,在 Java 编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等.redis),我们必须在这些外部资源使用完毕后,手动关闭它们. 因为外部资源不由 JVM 管理,无法享 ...

  5. Error: Invalid or corrupt jarfile SpringBootTemplate.jar

    当在尝试将SpringBoot打包成为Jar文件, 丢到linux服务器去运行的时候, 尝试在windows自带的CMD窗口命令行中运行jar文件的时候, 遇到了这样的问题. 错误的意思是: 无效 或 ...

  6. 发生 Configuration system failed to initialize 错误的一个特例

    一般情况下,.net 程序启动时发生 Configuration system failed to initialize 错误, 大都与 config 文件中 <configSections&g ...

  7. Linux Centos下MySQL主从Replication同步配置(一主一从)

    MySQL 主从复制概念MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点.MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据 ...

  8. Linux基础篇八:VIM

    新知识: 普通模式光标跳转: G     ##光标跳转到末端  (shift +g) gg   ##光标跳转到开端 Ngg 15gg  ##光标跳转到当前文本中的15行 $     ##光标移动到当前 ...

  9. Codeforces Round #599 (Div. 2)D 边很多的只有0和1的MST

    题:https://codeforces.com/contest/1243/problem/D 分析:找全部可以用边权为0的点连起来的全部块 然后这些块之间相连肯定得通过边权为1的边进行连接 所以答案 ...

  10. 2018-1 WebStorm最新版本破解激活方法

    在激活页面选择License Server,输入:http://idea.codebeta.cn,点击Activate即可激活. 如果失效用这个:  http://idea.ibdyr.com