聊聊fail-fast(并不是所有迭代时删除都会报错)

只记得在哪看到,说在集合对象的增强for时,不能进行增删改操作,不然就会报错。ps:不是所有的容器都会报错,有些是fail-safe
下面是我拷来的说明:
fail-fast:直接在容器上进行遍历,在遍历过程中,一旦发现容器中的数据被修改了,会立刻抛出ConcurrentModificationException异常导致遍历失败。java.util包下的集合类都是快速失败机制的, 常见的的使用fail-fast方式遍历的容器有HashMap和ArrayList等。
在使用迭代器遍历一个集合对象时,比如增强for,如果遍历过程中对集合对象的内容进行了修改(增删改),会抛出ConcurrentModificationException 异常.

并没有去深度理解为什么,只知道不要这么做就好,然后我今天看到这么一段代码,执行不会报错。

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("沉默王二");
    list.add("沉默是今晚的康桥");
    for (String str : list) {
        if ("沉默王二".equals(str)) {
            list.remove(str);
        }
    }
    System.out.println(list);
}

很难接受,但是不得不接受,所以决定看下到底遍历时报错是因为什么。

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

这是在遍历时进行增删改会报错的原因(ArrayList)。
modCount用于记录操作集合的次数
expectedModCount用于记录迭代开始之前的操作次数
所以在迭代之后,如果再继续修改集合,那就会出现modCount != expectedModCount,出现异常。上面就是为什么会报异常的原因。

为什么会进入进这个判断,我们可以反编译测试类的class,得到这个

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList();
    list.add("沉默王二");
    list.add("沉默是今晚的康桥");
    Iterator var2 = list.iterator();

    while(var2.hasNext()) {
        String str = (String)var2.next();
        if ("沉默王二".equals(str)) {
            list.remove(str);
        }
    }

    System.out.println(list);
}

for-each循环的本质也是使用iterator迭代,不过不同的是,remove使用了list的remove方法,remove改变modCount,再次执行next方法时,执行checkForComodification方法报错

那为什么最初的那个示例不报异常呢?
因为迭代时的hasNext长这样

    public boolean hasNext() {
        return cursor != size;
    }

cursor是现在迭代到哪一个了
size是集合大小
删除掉“沉默王二”之后,size大小是1,而cursor恰好也为1,以为自己迭代结束,然后跳出for了。自然就不会报错。虽然不会报错,但是结果不一定正确
例如

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("沉默王二");
    list.add("沉默王二");
    for (String str : list) {
        if ("沉默王二".equals(str)) {
            list.remove(str);
        }
    }
    System.out.println(list);
}

应该返回空集合,但是却返回“[沉默王二]”

只要size和cursor恰好一致,让迭代结束,就不会报错,所以一下例子也不会报错

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("沉默王二");
    list.add("沉默王三");
    list.add("沉默王四");
    for (String str : list) {
        if ("沉默王二".equals(str)) {
            list.remove(str);
            list.remove("沉默王三");
        }
    }
    System.out.println(list);
}

至于为什么使用Iterator的remove方法不会报错,是因为

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

在执行remove时,修改了expectedModCount。再判断时,自然不会错。

Stkcd [股票代码] ShortName [股票简称] Accper [统计截止日期] Typrep [报表类型编码] Indcd [行业代码] Indnme [行业名称] Source [公告来源] F060101B [净利润现金净含量] F060101C [净利润现金净含量TTM] F060201B [营业收入现金含量] F060201C [营业收入现金含量TTM] F060301B [营业收入现金净含量] F060301C [营业收入现金净含量TTM] F060401B [营业利润现金净含量] F060401C [营业利润现金净含量TTM] F060901B [筹资活动债权人现金净流量] F060901C [筹资活动债权人现金净流量TTM] F061001B [筹资活动股东现金净流量] F061001C [筹资活动股东现金净流量TTM] F061201B [折旧摊销] F061201C [折旧摊销TTM] F061301B [公司现金流1] F061302B [公司现金流2] F061301C [公司现金流TTM1] F061302C [公司现金流TTM2] F061401B [股权现金流1] F061402B [股权现金流2] F061401C [股权现金流TTM1] F061402C [股权现金流TTM2] F061501B [公司自由现金流(原有)] F061601B [股权自由现金流(原有)] F061701B [全部现金回收率] F061801B [营运指数] F061901B [资本支出与折旧摊销比] F062001B [现金适合比率] F062101B [现金再投资比率] F062201B [现金满足投资比率] F062301B [股权自由现金流] F062401B [企业自由现金流] Indcd1 [行业代码1] Indnme1 [行业名称1] 季度数据,所有沪深北上市公司的 分别包含excel、dta数据文件格式及其说明,便于不同软件工具对数据的分析应用 数据来源:基于上市公司年报及公告数据整理,或相关证券交易所、各部委、省、市数据 数据范围:基于沪深北证上市公司 A股(主板、中小企业板、创业板、科创板等)数据整理计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值