今天修改一个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. regex(python)

    正则表达式 #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/7/26 16:39 # @Author : jackendoff ...

  2. Linux服务器性能查看命令

    一.uptime命令 [root@#test~]# uptime15:26:42 up 101 days, 18:44,  3 users,  load average: 0.18, 0.22, 0. ...

  3. Hibernate/JPA中@Where使用时注意

    在使用Hibernate或者JPA时,我们经常会使用@Where注解实现查询过滤,在实体类上.实体属性上.查询语句上都有应用. 例如: @Where(clause = "status != ...

  4. MySQL修改表的默认字符集和修改表字段的默认字符集

    修改表的默认字符集: ALTER TABLE table_name DEFAULT CHARACTER SET character_name; 修改表字段的默认字符集: ALTER TABLE tab ...

  5. Docker系列二: docker常用命令总结

    https://docs.docker.com/reference/  官方命令总结地址 容器生命周期管理 1.docker run 创建一个新的容器并运行一个命令 docker run [optio ...

  6. 吴裕雄--天生自然python学习笔记:python处理word文档

    Office 文件是我们日常工作生活中都经常用到的文件格 式,其中以 Word 格式的文件最为常用 . Python 可通过 Win32com 纽件对 Micro so位 Office 文件 进行存取 ...

  7. C语言返回值

    C语言函数在结束的时候可以用一个数字表示运行的结果,这个数字就叫做函数的返回值. 主函数应该有一个返回值.如果这个返回值是0就表示程序希望计算机认为它正常结束,如果不是0就表示程序希望计算机认为它是出 ...

  8. LNOI 2019 旁观记

    真.懵逼. 退役选手进队了......我忽然后悔自己没去摸鱼...... 但是想一想毕竟有三分之一限制,我也搞不过那帮大佬...... 毕竟zhx还提前一周复习了一下呢 挂一个zhx大佬的博客 传送门 ...

  9. 数据结构中的顺序表和链表(Python语言)

    转载:https://blog.csdn.net/weixin_43187669/article/details/96426362 算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体. ...

  10. RPC 框架性能大比拼

    Dubbo 是阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成. Motan 是新浪微博开源的一个Java ...