今天在写一段很简单的代码,本来以为肯定没什么问题,然后直接跑的时候,吆,简单的一个List的操作报错了。仔细一看代码,确实有问题,但是一般真的是如果稍微不小心就会犯下面这种愚蠢的操作。

这里我把代码贴出来:

public static void main(String[] args)
{
List<Integer> list = new ArrayList<>(1);
list.add(1);
for (Integer a : list)
{
if (a == 1)
{
list.remove(a);
}
}
list.forEach(System.out::println);
}

上面的代码报错,我贴错误出来:







然后第一时间觉得有问题,这种遍历然后删除的操作应该要使用迭代器。然后我修改后改成了下面代码:

public static void main(String[] args)
{
List<Integer> list = new ArrayList<>(1);
list.add(1);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext())
{
Integer next = iterator.next();
if (next == 1)
{
list.remove(next);
}
}
list.forEach(System.out::println);
}

结果一运行同样报错,哎吆,一不小心还是直接去删除List了,然后再次修改才没了问题。最后修改的代码如下:

public static void main(String[] args)
{
List<Integer> list = new ArrayList<>();
list.add(1);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext())
{
Integer next = iterator.next();
if (next == 1)
{
iterator.remove();
}
}
list.forEach(System.out::println);
}

所以决心好好的研究下这个List在迭代过程中的删减操作为什么很容易报错。打开前面2次报错的代码异常出现的地方,可以清楚的看见报错的原因。

 

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

modCount,expectedModCount这2个变量是什么搞不懂,所以还是好好研究下吧。在这里我们看那段用迭代器遍历然后直接删除list中一个对象的那段代码,我们来研究一下:

打开JDK源码看一下Arraylist的add和remove操作:

public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} 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;
}
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; // clear to let GC do its work
}

前面我们已经知道,不管是使用fore循环还是说使用迭代器,List内部操作的都是hasnext()和next()方法。这里贴出源码:

private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size;
} @SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
} public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} @Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

上面的源码人家JDK里面的注释写的已经很清楚了,这里我们来整理一下:

cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出

lastRet:表示上一个访问的元素的索引

expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。

modCount:AbstractList类中的一个成员变量,默认是0。

该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。

所以我们在对List做迭代操作的过程中,如果这个时候来添加或者删除这个List,这个时候expectedModCount是原来的初始化时候的modCount值,但是modCount这个时候都自增改变了值了,所以肯定就报错了。具体如下图:





OK,现在这个时候我们已经知道了报错的原因了,那么为什么在直接使用interator迭代器来删除就没问题呢?

我们自己看一下Iterator源码里面的remove()方法,就明白了。

public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//下面这行是亮点,重新设值expectedModCount了。
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

好了,现在错误原因已经清楚了,和公司CTO聊了下这个问题,人家的原话是这么说的:

操作List在迭代的时候最好只是单纯的迭代,而不要试图去影响原来的那个List,特别是原来那个List的size长度。如果有需要最好也new一个新的list来处理过滤出来的数据最好。而且如果是单纯的要做查询就用Arraylist,如果要做插入和删除操作,最好用likenList。

我个人觉得说的很好很正确,以后编码时候如果要做过滤一个List这种操作时候,最好新new一个容器来搬数据,不要试图直接操作原来那个List。



List迭代过滤操作注意点的更多相关文章

  1. Stream01 定义、迭代、操作、惰性求值、创建流、并行流、收集器、stream运行机制

    1 Stream Stream 是 Java 8 提供的一系列对可迭代元素处理的优化方案,使用 Stream 可以大大减少代码量,提高代码的可读性并且使代码更易并行. 2 迭代 2.1 需求 随机创建 ...

  2. 如何对Backbone.Collection进行过滤操作

    首先我想说的是这篇文章的题目起的很怪,因为我不知道起个什么名字比较好.渲染列表是我们应用中最常见的操作了吧,在运用Backbone的应用中,我们一般会把列表作为一个Collcetion,然后指定一个V ...

  3. Trident的过滤操作

    1.过滤操作 只是判断某个tuple是否保留 无需跨网络,无需跨分区 不会改变tuple的结构,只是改变tuple的数量 2.需求 过滤掉不是订单的tuple. 其中订单中包含“IBEIfeng.gi ...

  4. stark组件之过滤操作【模仿Django的admin】

    一.先看下django的admin是如何实现过滤操作 首先在配置类中顶一个list_filter的列表,把要过滤的字段作为元素写i进去就可以了 class testbook(admin.ModelAd ...

  5. Fiddler过滤操作

    Fidller,不做过多的简介,其中的过滤操作肯定是绕不过去的.直接上图.

  6. 【SQL必知必会笔记(3)】SELECT语句的WHERE子句数据过滤操作

    上个笔记主要介绍了利用SELECT语句检索单个/多个/所有列,并利用DISTINCT关键字检索具有唯一性的值.利用LIMIT/OFFSET子句限制结果:以及利用ORDER BY子句排序检索出的数据,主 ...

  7. linux下拷贝命令中的文件过滤操作记录

    在日常的运维工作中,经常会涉及到在拷贝某个目录时要排查其中的某些文件.废话不多说,下面对这一需求的操作做一记录: linux系统中,假设要想将目录A中的文件复制到目录B中,并且复制时过滤掉源目录A中的 ...

  8. 通过反射,对javabean属性进行过滤操作

    /** * 根据属性名获取属性值 * @param fieldName 属性名 * @param o 传入对象 * @return */ private Object getFieldValueByN ...

  9. vue 对 v-for 中数组进行过滤操作

    之前写angularjs的时候,filter是可以直接在ng-repeat中使用.但是到了vue好像这个不起作用. 具体解决办法: 加一个计算属性: computed:{ filterData: fu ...

随机推荐

  1. vmware 解决 authentication token manipulation error

    vmvare虚拟机长时间未使用,导致再次登录的时候密码忘了,无法登录. 启动时长按shift,进入root(recovery)模式, (recovery mode),进入"Recovery ...

  2. Django-Views模块详解

    http请求中产生的两个核心对象 http请求: HttpRequest http响应: HttpResponse 所在位置 django.http httpRequest属性: HttpReques ...

  3. 关于soapui简介与入门

    SoapUI简介 SoapUI是一个开源测试工具,通过soap/http来检查.调用.实现Web Service的功能/负载/符合性测试.该工具既可作为一个单独的测试软件使用,也可利用插件集成到Ecl ...

  4. Java与算法之(1) - 冒泡排序

    冒泡排序法的原理是,每次比较相邻的两个元素,如果它们的顺序错误就把它们交换过来. 例如对4 3 6 2 7 1 5这7个数字进行从小到大的排序,从最左侧开始,首先比较4和3 因为是从小到大排序,4和3 ...

  5. Java DB 访问之(四) spring mvc 组合mybatis

    说明 本项目采用 maven 结构,主要演示了 spring mvc + mybatis,controller 获取数据后以json 格式返回数据. 项目结构 包依赖 与说明 pom文件: <p ...

  6. Cnm%(个人模版)

    Cnm%: #include<stdio.h> #include<string.h> #include<vector> using namespace std; # ...

  7. bzoj 4605: 崂山白花蛇草水

    Description 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实 力,他轻松地进了山东省省队,现在便是他履行诺言的时 ...

  8. C++ STL之min_element()与max_element()(取容器中的最大最小值)

    min_element()和max_element 头文件:#include<algorithm> 作用:返回容器中最小值和最大值.max_element(first,end,cmp);其 ...

  9. Uva - 12050 Palindrome Numbers【数论】

    题目链接:uva 12050 - Palindrome Numbers 题意:求第n个回文串 思路:首先可以知道的是长度为k的回文串个数有9*10^(k-1),那么依次计算,得出n是长度为多少的串,然 ...

  10. Java学习之封装

    Java是一种面向对象的编程语言,对于面向对象的编程语言中有一种思想叫做封装. 封装是一种很重要的思想,今天在看教学视频时,觉得视频中的例子很好的解释了封装的重要性,能够提高程序的健壮性. 视频中以人 ...