Java中ArrayList循环遍历并删除元素的陷阱
ava中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下。
先写个测试代码:
- import java.util.ArrayList;
- public class ArrayListRemove {
- public static void main(String[] args) {
- ArrayList<String> list = new ArrayList<String>();
- list.add("a");
- list.add("bb");
- list.add("bb");
- list.add("ccc");
- list.add("ccc");
- list.add("ccc");
- remove(list);
- for (String s : list) {
- System.out.println("element : " + s);
- }
- }
- public static void remove(ArrayList<String> list) {
- // TODO:
- }
- }
错误写法示例一:
- public static void remove(ArrayList<String> list) {
- for (int i = 0; i < list.size(); i++) {
- String s = list.get(i);
- if (s.equals("bb")) {
- list.remove(s);
- }
- }
- }
这种最普通的循环写法执行后会发现有一个“bb”的字符串没有删掉。
错误写法示例二:
- public static void remove(ArrayList<String> list) {
- for (String s : list) {
- if (s.equals("bb")) {
- list.remove(s);
- }
- }
- }
这种for each写法会发现报出著名的并发修改异常java.util.ConcurrentModificationException。
要分析产生上述错误现象的原因唯有翻一翻jdk的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:
- 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;
- }
按一般执行路径会走到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
- }
可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第二个元素字符串bb时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也是字符串bb)至当前位置,导致下一次循环遍历时后一个字符串bb并没有遍历到,所以无法删除。
针对这种情况可以倒序删除的方式来避免:
- public static void remove(ArrayList<String> list) {
- for (int i = list.size() - 1; i >= 0; i--) {
- String s = list.get(i);
- if (s.equals("bb")) {
- list.remove(s);
- }
- }
- }
因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。
而错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):
- public Iterator<E> iterator() {
- return new Itr();
- }
这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator<E>,看这个类的next方法:
- public E next() {
- checkForComodification();
- try {
- E next = get(cursor);
- lastRet = cursor++;
- return next;
- } catch (IndexOutOfBoundsException e) {
- checkForComodification();
- throw new NoSuchElementException();
- }
- }
第一行checkForComodification方法:
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法把修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。
- public static void remove(ArrayList<String> list) {
- Iterator<String> it = list.iterator();
- while (it.hasNext()) {
- String s = it.next();
- if (s.equals("bb")) {
- it.remove();
- }
- }
- }
Java中ArrayList循环遍历并删除元素的陷阱的更多相关文章
- 【转】ArrayList循环遍历并删除元素的常见陷阱
转自:https://my.oschina.net/u/2249714/blog/612753?p=1 在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出b ...
- ArrayList循环遍历并删除元素的常见陷阱
在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出bug.不妨把这个问题当做一道面试题目,我想一定能难道不少的人.今天就给大家说一下在ArrayList循环 ...
- ArrayList循环遍历并删除元素的几种情况
如下代码,想要循环删除列表中的元素b,该怎么处理? public class ListDemo { public static void main(String[] args) { ArrayList ...
- Java中List循环遍历的时候删除当前对象(自己)
方法一 Java代码 ArrayList<String> list = new ArrayList<String>(); list.add(" ...
- Java HashMap 如何正确遍历并删除元素
(一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap<K ...
- Java中ArrayList边遍历边修改
用for-each 边遍历ArrayList 边修改时: public static void main(String[] args) { ArrayList<String> list = ...
- Java中for循环遍历List的两种方法
我们平常使用的方法: List<WebElement> element = driver.findElements(By.tagName("input")); ...
- 【Java】List遍历时删除元素的正确方式
当要删除ArrayList里面的某个元素,一不注意就容易出bug.今天就给大家说一下在ArrayList循环遍历并删除元素的问题.首先请看下面的例子: import java.util.ArrayLi ...
- 关于java中ArrayList的快速失败机制的漏洞——使用迭代器循环时删除倒数第二个元素不会报错
一.问题描述 话不多说,先上代码: public static void main(String[] args) throws InterruptedException { List<Strin ...
随机推荐
- Uva101-STL模拟
一道有点复杂的STL模拟题,对STL迭代器不太熟悉改了好久,最后总算A了出来. 感觉用数组更方便...但是为了练习STL嘛 对比白书上的代码,我写的还是傻了点.一开始没有理解四个操作的意思,单纯的模拟 ...
- MT【240】6*6放黑白子
$6*6$的方格中放三个完全相同的黑子和三个完全相同的白子,要求每行每列都有一个棋子,且每一格只有一个棋子.问有多少不同放法? 解:$\dfrac{36*25*16*9*4*1}{3!*3!}=144 ...
- android 开发中 sdk 无法更新
现在用到android 的多个版本适配 , 换了个新环境 , 重新配置了android 的开发环境,哪想到遇到了很多小问题. 今天又遇到了 android sdk manager 无法更新的问题. ...
- BZOJ 4009: [HNOI2015]接水果
4009: [HNOI2015]接水果 Time Limit: 60 Sec Memory Limit: 512 MBSubmit: 636 Solved: 300[Submit][Status] ...
- 面试 -- Http协议相关(转载)
http请求由三部分组成,分别是:请求行.消息报头.请求正文 HTTP(超文本传输协议)是一个基于请求与响应模式的.无状态的.应用层的协议,常基于TCP的连接方式,HTTP1.1版本中给出一种持续连接 ...
- C# 面向对象的封装、继承、多态
一.封装: 封装:把客观的事物封装成类,使用和修改方便: 作用和结构体使用方法相似,程序执行流程不同: 要点:成员变量,属性,成员方法,构造函数,成员方法的静态和非静态,命名空间,常用的访问修饰符pu ...
- A1039. Course List for Student
Zhejiang University has 40000 students and provides 2500 courses. Now given the student name lists o ...
- 【模板】spfa
代码如下 #include <bits/stdc++.h> using namespace std; const int maxv=1e4+10; const int maxe=5e5+1 ...
- gcc-linaro-arm-linux-gnueabihf交叉编译器配置
系统Ubuntu14.04 版本:gcc 版本 4.7.3 20130328 (prerelease) (crosstool-NG linaro-1.13.1-4.7-2013.04-20130415 ...
- 织梦DedeCMS信息发布员发布文章阅读权限不用审核自动开放亲测试通过!
文章发布员在织梦dedecms后台添加文章时却要超级管理员审核,这无疑是增加了没必要的工作. 登录该账号发布文章你会发现该文章显示的是待审核稿件,且并没有生成静态文件,在前台是看不到这篇文章的,而多数 ...