今天修改一个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. nginx配置文件说明(包含IP黑名单、代理反射、负载均衡的配置)

    先看下nginx配置文件整体结构 图片来源51cto 配置文件及注解: #运行用户 主模块指令,指定Nginx Worker进程运行用户以及用户组,默认由nobody账号运行 user nobody; ...

  2. HTTP知识整理

    HTTP协议 HTTP协议的主要特点可概括如下: 1.支持客户/服务器模式. 2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径.请求方法常用的有GET.HEAD.POST.每种方法规定了客 ...

  3. mint 18中安装最新的R

    mint 中默认的R版本有点老,升级最新版方法如下: 先卸载 sudo apt-get remove r-base-core 添加mint 18 识别的源 sudo echo "deb ht ...

  4. 【转】浅述WinForm多线程编程与Control.Invoke的应用

    环境:VS2008.C#3.0 在WinForm开发中,我们通常不希望当窗体上点了某个按钮执行某个业务的时候,窗体就被卡死了,直到该业务执行完毕后才缓过来.一个最直接的方法便是使用多线程.多线程编程的 ...

  5. Opencv笔记(六)——把滑动条当调色板

    学习目标: 学会把滑动条绑定到 OpenCV 的窗口. 学习函数:cv2.getTrackbarPos(), cv2.creatTrackbar()等. 简单演示: 通过调节滑动条来设定画板颜色.我们 ...

  6. WMS出库单重复

    发货通知单?WMS备货单选项勾选 不自动复制?新增?

  7. 华硕X450j清灰教程

    近期本人电脑咳嗽,电脑发烧,风扇呼呼的吹(电脑风扇好像出了问题),去了电脑上商城买了一支止咳糖浆(HEATSINK COMPOUNDS)硅脂, 废话不多说了 1 ,当然要把电脑关了,卸下三排螺丝,然后 ...

  8. crm项目-stark组件分析

    ###############    stark组件     ################ """ 这个stark组件是非常神奇的 1,独立的一个组件 2,没有mod ...

  9. ibatis in语句参数传入方法

    第一种:传入参数仅有数组        <select id="GetEmailList_Test"  resultClass="EmailInfo_"& ...

  10. Rearrangement

    In a two dimensional array of integers of size 2×n2 \times n2×n, is it possible to rearrange integer ...