java中List元素移除元素的那些坑(上)

简介: 本文主要介绍,java中list集合移除元素的那些坑,今天小编就和大家一起来了解一下吧!


一、问题案例


1.1、for循环移除

public static void main(String[] args) {
  List<String> list = new ArrayList<String>();
  list.add("11");
  list.add("11");
  list.add("12");
  list.add("13");
  list.add("14");
  list.add("15");
  list.add("16");
  System.out.println("原始list元素:"+ list.toString());
  CopyOnWriteArrayList<String> copyList = new CopyOnWriteArrayList<>(list);
  //通过下表移除等于11的元素
  for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    if("11".equals(item)) {
      list.remove(i);
    }
  }
  System.out.println("通过下表移除后的list元素:"+ list.toString());
  //通过对象移除等于11的元素
  for (int i = 0; i < copyList.size(); i++) {
    String item = copyList.get(i);
    if("11".equals(item)) {
      copyList.remove(item);
    }
  }
  System.out.println("通过对象移除后的list元素:"+ list.toString());
}

输出结果:

原始list元素:[11, 11, 12, 13, 14, 15, 16]
通过下表移除后的list元素:[11, 12, 13, 14, 15, 16]
通过对象移除后的list元素:[11, 12, 13, 14, 15, 16]

有没有发现有蹊跷的地方?


从输出结果可以看的出,移除后的元素,并没有把内容为 11 的都移除掉!


发生了什么?


删除了第一个 11 后,集合里的元素个数减1,后面的元素往前移了1位,此时,第二个 11 已经移到了索引 index=1的位置,而此时i马上i++了,list.get(i)获得的是数据  12 。同时 list.size() 的值也在减小。所以最后输出那个结果。

public static void main(String[] args) {
  List<String> list = new ArrayList<String>();
  list.add("11");
  list.add("11");
  list.add("12");
  list.add("13");
  list.add("14");
  list.add("15");
  list.add("16");
  System.out.println("原始list元素:"+ list.toString());
  //通过对象移除等于11的元素
  for (String item : list) {
    if("11".equals(item)) {
      list.remove(item);
    }
  }
  System.out.println("通过对象移除后的list元素:"+ list.toString());
}

输出结果:

85.jpg

抛ConcurrentModificationException异常!


foreach 写法实际上是对的 Iterable hasNext next 方法的简写。因此我们从List.iterator() 着手分析,跟踪 iterator() 方法,该方法返回了 Itr 迭代器对象。


找到  List  的迭代器类

public Iterator<E> iterator() {
  return new Itr();
}

Itr 对象

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;
    Itr() {}
    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();
    }
}

通过代码我们发现 Itr ArrayList 中定义的一个私有内部类,在 nextremove方法中都会调用 checkForComodification 方法,该方法的作用是判断 modCount != expectedModCount是否相等,如果不相等则抛出ConcurrentModificationException异常。每次正常执行 remove 方法后,都会对执行 expectedModCount = modCount 赋值,保证两个值相等!


那么问题基本上已经清晰了,在 foreach 循环中执行 list.remove(item);,对 list 对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,因此抛出了ConcurrentModificationException 异常。

相关文章
|
1天前
|
人工智能 Java
Java练习题-输出二维数组对角线元素和
Java练习题-输出二维数组对角线元素和
24 1
|
1天前
|
Java
java8中List对象转另一个List对象
java8中List对象转另一个List对象
41 0
|
1天前
|
Java
java实现向有序数组中插入一个元素
java实现向有序数组中插入一个元素
10 0
|
1天前
|
存储 安全 算法
Java一分钟之-Java集合框架入门:List接口与ArrayList
【5月更文挑战第10天】本文介绍了Java集合框架中的`List`接口和`ArrayList`实现类。`List`是有序集合,支持元素重复并能按索引访问。核心方法包括添加、删除、获取和设置元素。`ArrayList`基于动态数组,提供高效随机访问和自动扩容,但非线程安全。文章讨论了三个常见问题:索引越界、遍历时修改集合和并发修改,并给出避免策略。通过示例代码展示了基本操作和安全遍历删除。理解并正确使用`List`和`ArrayList`能提升程序效率和稳定性。
7 0
|
1天前
|
存储 安全 Java
【JAVA基础篇教学】第八篇:Java中List详解说明
【JAVA基础篇教学】第八篇:Java中List详解说明
|
1天前
|
存储 安全 Java
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
|
1天前
|
XML 前端开发 Oracle
16:JSP简介、注释与Scriptlet、Page指令元素、Include操作、内置对象、四种属性-Java Web
16:JSP简介、注释与Scriptlet、Page指令元素、Include操作、内置对象、四种属性-Java Web
14 2
|
1天前
|
安全 Java
循环的时候去删除集合中的元素 java.util.ConcurrentModificationException
循环的时候去删除集合中的元素 java.util.ConcurrentModificationException
|
1天前
|
Java
java Map删除值为null的元素
java Map删除值为null的元素
|
1天前
|
Java API
【亮剑】三种有效的方法来删除List中的重复元素Java的List
【4月更文挑战第30天】本文介绍了三种Java中删除List重复元素的方法:1) 使用HashSet,借助其不允许重复值的特性;2) 利用Java 8 Stream API的distinct()方法;3) 对自定义对象重写equals()和hashCode()。每种方法都附带了代码示例,帮助理解和应用。