List的迭代器的理解

目录

目的

List遍历和删除

使用基本的for循环遍历删除

使用Iterator遍历,list进行删除

iterator原理与fail-fast原理

思考


目的

java中List的遍历和删除问题是个老生常谈的问题了,似懂非懂的很多,经得住几个为什么的很少,本文解释几个问题,有助于去贯通List中iterator的原理,fail-fast机制,这两点懂了可以应付各种遍历和删除的为什么?

List遍历和删除

使用基本的for循环遍历删除

很多开发人员已经被灌输了思想,必须使用iterator才可以遍历删除,其实不然,看如下代码

    // 使用基本的for循环删除
    public static void deleteFor() {
        List<String> names = new ArrayList<>();
        names.add("jack");
        names.add("tim");
        names.add("lili");
        names.add("tim");
        names.add("lucy");
        names.iterator();
        String name;
        for (int i = 0; i < names.size(); ++i) {
            System.out.println(name = names.get(i));
            if ("tim".equals(names.get(i))) {
                // 删除后list整体向前移动一位,i后退一位,保证不跳跃
                names.remove(i--);
            }
        }
        System.out.println(names.toString());
    }

这种操作不会有问题,只是要注意避免游标变量的跳跃现象。

使用Iterator遍历,list进行删除

先看一段代码,这段代码我们使用增强for遍历,并且使用List的remove方法删除遍历元素

// 会不会产生想象中的ConcurrentModificationException?为什么?
public static void deleteStrongFor() {
        List<String> names = new ArrayList<>();
        names.add("jack");
        names.add("tim");
        names.add("lili");
        for (String name : names) {
            System.out.println(name);
            if ("tim".equals(name)) {
                names.remove(name);
            }
        }
        System.out.println(names.toString());
    }
// 会不会产生想象中的ConcurrentModificationException?为什么?
public static void deleteStrongFor() {
        List<String> names = new ArrayList<>();
        names.add("jack");
        names.add("tim");
        names.add("lili");
        for (String name : names) {
            System.out.println(name);
            if ("lili".equals(name)) {
                names.remove(name);
            }
        }
        System.out.println(names.toString());
    }

结果:第一段代码不会产生异常,第二段代码会产生异常。

原因:第一段代码不会产生异常的原理,没有触发fail-fast机制,iterator的原理和fail-fast机制不是那么完美!第二段代码会产生异常,fail-fast机制生效。

首先看下增强型for循环在编译后的代码,借助idea查看上述代码片段的.class文件,发现增强型for编译后就是使用while和iterator进行遍历的。所以要解释前面的现象就得分析一下iterator的原理。

public static void deleteStrongFor() {
        List<String> names = new ArrayList();
        names.add("jack");
        names.add("tim");
        names.add("lili");
        Iterator var1 = names.iterator();

        while(var1.hasNext()) {
            String name = (String)var1.next();
            System.out.println(name);
            if ("tim".equals(name)) {
                names.remove(name);
            }
        }

        System.out.println(names.toString());
    }

iterator原理与fail-fast原理

具体原理看ArrayList.Iterator的源码,如下

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        // 初始化时为0
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        // 初始化为list外部类的modCount
        int expectedModCount = modCount;
        
        Itr() {}
        // 游标和size不相同则返回true
        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();
            // 返回结果之前,游标移到下一位置,等待下一次hasNext判断
            cursor = i + 1;
            // 返回结果之前,lastRet已确认
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                // 若删除操作,cursor被修改为上一个返回位置,保证了不跳跃
                cursor = lastRet;
                // next()方法会重置这个lastRet
                lastRet = -1;
                // 欺骗或者说完成fail-fast机制
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
}
  • iterator中的cursor是下一个next()将要返回的元素的下标,初始化为0,第一次运行next返回第一个元素前,就已经移动到第二个元素的下标,就是1,依次类推。
  • hasNext的判断依据 cursor!=size
  • iterator中的  expectedModCount标记操作数,与List自身的modCount对比,不一样则ConcurrentModificationException,expectedModCount的值在获得list的iterator时初始化为list的modCount,本例中添加三个元素后,两者的值都为3
  • fail-fast机制只在next和remove中进行判断

基于以上三点复现两代码的必要过程:

第一段代码

  • 删除"tim"元素后,cursor=2,list的size=2,hasNext的结果为false,循环跳出不执行后面的next,所以没有意料中的异常

第二段代码

  • 删除"tim"元素后,cursor=3,list的size=2,hasNext的结果为true,modCount=4(因为使用的时list的remove方法),expectedModCount=3(因为删除不是使用iterator进行的,expectedModCount不变)
  • 由于hasNext为true,进入next方法,显而易见ConcurrentModificationException出现了

思考

  1. fail-fast的设计目的是快速甄别出逻辑上对集合的“并发”操作异常,而不是解决问题。否则使用基本的for可能抛出得是OutofBound之类的异常,出现此类异常时可能要花点时间去探究,fail-fast尽量将这种隐蔽的逻辑漏洞暴露出来,方便解决问题。
  2. 其实这种iterator.remove()是不是和基本的for循环中删除i--的机制很像,iterator接口注释中写明其第一个优点就是可以一边遍历,一边删除,但使用基本for也可以实现。
  3. fail-fast机制是否需要进一步加强在hasNext中?说这个的原因是,看下面代码

将list最后两个元素都设置为“tim”,那么在删除时,只会删除倒数第二个tim, 最后一个留下了,程序也没有异常,程序员也觉得没问题,其实还是有逻辑上的问题的。

public static void deleteStrongFor() {
        List<String> names = new ArrayList<>();
        names.add("jack");
        names.add("tim");
        names.add("tim");
        for (String name : names) {
            System.out.println(name);
            if ("tim".equals(name)) {
                // 这是个错误的示例
                // 只删除了倒数第二个tim,程序依然正常运行
                names.remove(name);
            }
        }
        System.out.println(names.toString());
    }

欢迎批评指正,共同进步!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值