arraylist java 遍历删除,ArrayList边遍历边删除?

主要方法

ArrayList是一个泛型容器,先介绍下它的主要方法:public boolean add(E e) //添加元素到末尾

public boolean isEmpty() //判断是否为空

public int size() //获取长度

public E get(int index) //访问指定位置的元素

public int indexOf(Object o) //查找元素, 如果找到,返回索引位置,否则返回-1

public int lastIndexOf(Object o) //从后往前找

public boolean contains(Object o) //是否包含指定元素,依据是equals方法的返回值

public E remove(int index) //删除指定位置的元素, 返回值为被删对象

//删除指定对象,只删除第一个相同的对象,返回值表示是否删除了元素

//如果o为null,则删除值为null的元素

public boolean remove(Object o)

public void clear() //删除所有元素

//在指定位置插入元素,index为0表示插入最前面,index为ArrayList的长度表示插到最后面

public void add(int index, E element)

public E set(int index, E element) //修改指定位置的元素内容

// Collection.java

default boolean removeIf(Predicate super E> filter) //删除符合filter条件的元素

边遍历边删除需求:在包含0,1,2,3,4,5的集合中,删除大于2的数字

错误例子1package cn.dhbin.arraylist;

import java.util.ArrayList;

import java.util.List;

/**

* @author donghaibin

* @date 2019/11/20

*/

public class Demo {

public static void main(String[] args) {

List list = new ArrayList<>();

list.add(0);

list.add(1);

list.add(2);

list.add(3);

list.add(4);

list.add(5);

for (int i = 0; i < list.size(); i++) {

System.out.println("remove之前集合大小: " + list.size());

Integer item = list.get(i);

if (item > 2) {

list.remove(item);

System.out.println("remove之后集合大小:" + list.size());

}

}

System.out.println(list);

}

}

一个for循环遍历,如果item > 2就执行remove,看上去似乎没有问题的样子,也不会出现报错,但是不符合题目的要求

a684f4c53e9ba366834bbca9420c73ea.png

9fe5277e0f02cc61cde11175a95b37f1.png

原因是remove方法更新了size的值,即集合的大小,集合的元素也向前移动了一位,变成[0,1,2,4,5],再执行下一个循环时,i++ = 4,删除的是5,4 < list.size跳出了循环,导致最后输出的结果是[0, 1, 2, 4]

错误例子2package cn.dhbin.arraylist;

import java.util.ArrayList;

import java.util.List;

/**

* @author donghaibin

* @date 2019/11/20

*/

public class Demo {

public static void main(String[] args) {

List list = new ArrayList<>();

list.add(0);

list.add(1);

list.add(2);

list.add(3);

list.add(4);

list.add(5);

for (Integer integer : list) {

if (integer > 2) {

list.remove(integer);

}

}

}

}

这个例子执行直接报ConcurrentModificationException异常了。哈?一个单线程的程序怎么会发生了并发异常,为什么呢?

foreach底层实现其实就是迭代器,反编译上面的代码如下://

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//

package cn.dhbin.arraylist;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

public class Demo {

public Demo() {

}

public static void main(String[] args) {

List list = new ArrayList();

list.add(0);

list.add(1);

list.add(2);

list.add(3);

list.add(4);

list.add(5);

Iterator var2 = list.iterator();

while(var2.hasNext()) {

Integer integer = (Integer)var2.next();

if (integer > 2) {

list.remove(integer);

}

}

}

}

因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。所谓结构性变化就是添加、插入和删除元素,只是修改元素内容不算结构性变化。如何避免这个异常呢?可以用迭代器的remove方法,如下所示:package cn.dhbin.arraylist;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

/**

* @author donghaibin

* @date 2019/11/20

*/

public class Demo {

public static void main(String[] args) {

List list = new ArrayList<>();

list.add(0);

list.add(1);

list.add(2);

list.add(3);

list.add(4);

list.add(5);

Iterator iterator = list.iterator();

while (iterator.hasNext()) {

Integer next = iterator.next();

if (next > 2) {

iterator.remove();

}

}

}

}

在JDK8提供了更方便的方法,removeIf内部实现和上面的代码类似list.removeIf(next -> next > 2);

迭代器实现的原理

ArrayList中iterator方法的实现,代码为:public Iterator iterator() {

return new Itr();

}

Itr是ArrayList的内部类,实现了Iterator接口,声明为:private class Itr implements Iterator

它有三个成员变量,为int cursor; // 下一个要返回的元素位置

int lastRet = -1; // 最后一个返回的索引位置,如果没有,为-1

int expectedModCount = modCount;变量说明cursor下一个要返回的元素位置

lastRet最后一个返回的索引位置

modCountArrayList的变量,记录修改的次数

expectedModCount期望的修改次数,初始化为外部类当前的修改次数modCount

回顾一下,成员内部类可以直接访问外部类的实例变量。每次发生结构性变化的时候modCount都会增加,而每次迭代器操作的时候都会检查expectedModCount是否与modCount相同,这样就能检测出结构性变化。

看下iterator中的每个方法,hasNext方法比较简单cursor和size比较,不等于size说明还有下一位,返回truepublic boolean hasNext() {

return cursor != size;

}

next方法: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,下一个要返回元素位置 + 1

cursor = i + 1;

// 更新lastRet

return (E) elementData[lastRet = i];

}

首先调用了checkForComodification,代码为:final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

检查modCount是否等于expectedModCount,如果不等于说明发生了结构性的变化,抛出异常。

remove方法:public void remove() {

if (lastRet < 0)

throw new IllegalStateException();

// 检查是否发生结构性变化

checkForComodification();

try {

// 删除当前元素

ArrayList.this.remove(lastRet);

// 更新cursor=lastRet,因为容器向前移动了一位

cursor = lastRet;

// 复位

lastRet = -1;

// 把expectedModCount = modCount,同步修改次数。

expectedModCount = modCount;

} catch (IndexOutOfBoundsException ex) {

throw new ConcurrentModificationException();

}

}

它调用了ArrayList的remove方法,但同时更新了cursor、lastRet和expectedModCount的值,所以它可以正确删除。不过,需要注意的是,调用remove方法前必须先调用next。

总结

ArrayList的remove方法会修改容器的大小,直接for循环会出现与要求不符的结果。更推荐使用迭代器返回容器的元素。foreach语法更为简洁一些,更重要的是,迭代器语法更为通用,它适用于各种容器类。此外,迭代器表示的是一种关注点分离的思想,将数据的实际组织方式与数据的迭代遍历相分离,是一种常见的设计模式。需要访问容器元素的代码只需要一个Iterator接口的引用,不需要关注数据的实际组织方式,可以使用一致和统一的方式进行访问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值