首页 南方天气预报正文

黄奕,为什么阿里巴巴制止在 foreach 循环里进行元素的 remove/add 操作-雷火电竞登录

admin 南方天气预报 2019-05-14 277 0

摘要

foreach循环(Foreach loop)是核算机编程言语中的一种操控流程句子,一般用来循环遍历数组或调集中的元素。

在阿里巴巴Java开发手册中,有这样一条规矩:

可是手册中并没有给出详细原因,本文就来深入剖析一下该规矩背面的考虑。

1

foreach循环

foreach循环(Foreach loop)是核算机编程言语中的一种操控流程句子,一般用来循环遍历数组或调集中的元素。

Java言语从JDK 1.5.0开端引进foreach循环。在遍历数组、调集方面,foreach为开发人员供给了极大的便利。一般也被称之为增强for循环。

foreach 语法格局如下:

以下实例演示了 一般for循环 和 foreach循环运用:

以上代码运转输出成果为:

能够看到,运用foreach语法遍历调集或许数组的时分,能够起到和一般for循环相同的作用,而且代码愈加简练。所以,foreach循环也一般也被称为增强for循环。

可是,作为一个合格的程序员,咱们不只要知道什么是增强for循环,还需求知道增强for循环的原理是什么?

其实,增强for循环也是Java给咱们供给的一个语法糖,假如将以上代码编译后的class文件进行反编译(运用jad东西)的话,能够得到以下代码:

能够发现,本来的增强for循环,其实是依靠了while循环和Iterator完成的。(请记住这种完成办法,后边会用到!)

2

问题重现

标准中指出不让咱们在foreach循环中对调集元素做add/remove操作,那么,咱们测验着做一下看看会发作什么问题。

以上代码,首要运用双括弧语法(double-brace syntax)树立并初始化一个List,其间包括四个字符串,分别是Hollis、hollis、HollisChuang和H。

然后运用一般for循环对List进行遍历,删去List中元素内容等于Hollis的元素。然后输出List,输出成果如下:

[hollis, HollisChuang, H]

以上是运用一般的for循环在遍历的一起进行删去,那么,咱们再看下,假如运用增强for循环的话会发作什么:

以上代码,运用增强for循环遍历元素,并测验删去其间的Hollis字符串元素。运转以上代码,会抛出以下反常:

java.util.ConcurrentModificationException

相同的,读者能够测验下在增强for循环中运用add办法增加元素,成果也会相同抛出该反常。

之所以会呈现这个反常,是因为触发了一个Java调集的过错检测机制——fail-fast 。

3

fail-fast

接下来,咱们就来剖析下在增强for循环中add/remove元素的时分会抛出java.util.ConcurrentModificationException的原因,即解说下究竟什么是fail-fast进制,fail-fast的原理等。

fail-fast,即快速失利,它是Java调集的一种过错检测机制。当多个线程对调集(非fail-safe的调集类)进行结构上的改动的操作时,有可能会发作fail-fast机制,这个时分就会抛出ConcurrentModificationException(当办法检测到目标的并发修正,但不答应这种修正时就抛出该反常)。

一起需求留意的是,即便不是多线程环境,假如单线程违反了规矩,相同也有可能会抛出改反常。

那么,在增强for循环进行元素删去,是怎么违反了规矩的呢?

要剖析这个问题,咱们先将增强for循环这个语法糖进行解糖(运用jad对编译后的class文件进行反编译),得到以下代码:

然后运转以上代码,相同会抛出反常。咱们来看一下ConcurrentModificationException的完好仓库:

经过反常仓库咱们能够到,反常发作的调用链ForEachDemo的第23行,Iterator.next调用了Iterator.checkForComodification办法 ,而反常便是checkForComodification办法中抛出的。

其实,经过debug后,咱们能够发现,假如remove代码没有被履行过,iterator.next这一行是一向没报错的。抛反常的机遇也正是remove履行之后的的那一次next办法的调用。

咱们直接看下checkForComodification办法的代码,看下抛出反常的原因:

代码比较简略,modCount != expectedModCount的时分,就会抛出ConcurrentModificationException

那么,就来看一下,remove/add 操作室怎么导致modCount和expectedModCount不相等的吧。

4

remove/add 做了什么

首要,咱们要搞清楚的是,究竟modCount和expectedModCount这两个变量都是个什么东西。

经过翻源码,咱们能够发现:

  • modCount是ArrayList中的一个成员变量。它表明该调集实践被修正的次数。

  • expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表明这个迭代器希望该调集被修正的次数。其值是在ArrayList.iterator办法被调用的时分初始化的。只要经过迭代器对调集进行操作,该值才会改动。

  • Itr是一个Iterator的完成,运用ArrayList.iterator办法能够获取到的迭代器便是Itr类的实例。

他们之间的联络如下:

其实,看到这儿,大约许多人都能猜到为什么remove/add 操作之后,会导致expectedModCount和modCount不想等了。

经过翻阅代码,咱们也能够发现,remove办法中心逻辑如下:

能够看到,它只修正了modCount,并没有对expectedModCount做任何操作。

简略总结一下,之所以会抛出ConcurrentModificationException反常,是因为咱们的代码中运用了增强for循环,而在增强for循环中,调集遍历是经过iterator进行的,可是元素的add/remove却是直接运用的调集类自己的办法。这就导致iterator在遍历的时分,会发现有一个元素在自己不知不觉的情况下就被删去/增加了,就会抛出一个反常,用来提示用户,可能发作了并发修正。

5

正确姿态

至此,咱们介绍清楚了不能在foreach循环体中直接对调集进行add/remove操作的原因。

可是,许多时分,咱们是有需求需求过滤调集的,比方删去其间一部分元素,那么应该怎么做呢?有几种办法可供参考:

1、直接运用一般for循环进行操作

咱们说不能在foreach中进行,可是运用一般的for循环仍是能够的,因为一般for循环并没有用到Iterator的遍历,所以压根就没有进行fail-fast的查验。

2、直接运用Iterator进行操作

除了直接运用一般for循环以外,咱们还能够直接运用Iterator供给的remove办法。

假如直接运用Iterator供给的remove办法,那么就能够修正到expectedModCount的值。那么就不会再抛出反常了。其完成代码如下:

3、运用Java 8中供给的filter过滤

Java 8中能够把调集转换成流,关于流有一种filter操作, 能够对原始 Stream 进行某项测验,经过测验的元素被留下来生成一个新 Stream。

4、直接运用fail-safe的调集类

在Java中,除了一些一般的调集类以外,还有一些采用了fail-safe机制的调集类。这样的调集容器在遍历时不是直接在调集内容上拜访的,而是先仿制原有调集内容,在复制的调集上进行遍历。

因为迭代时是对原调集的复制进行遍历,所以在遍历过程中对原调集所作的修正并不能被迭代器检测到,所以不会触发ConcurrentModificationException。

根据复制内容的长处是防止了ConcurrentModificationException,但相同地,迭代器并不能拜访到修正后的内容,即:迭代器遍历的是开端遍历那一刻拿到的调集复制,在遍历期间原调集发作的修正迭代器是不知道的。

java.util.concurrent包下的容器都是安全失利,能够在多线程下并发运用,并发修正。

5、运用增强for循环其实也能够

假如,咱们十分确定在一个调集中,某个行将删去的元素只包括一个的话, 比方对Set进行操作,那么其实也是能够运用增强for循环的,只要在删去之后,马上完毕循环体,不要再继续进行遍历就能够了,也便是说不让代码履行到下一次的next办法。

以上这五种办法都能够防止触发fail-fast机制,防止抛出反常。假如是并发场景,主张运用concurrent包中的容器,假如是单线程场景,Java8之前的代码中,主张运用Iterator进行元素删去,Java8及更新的版别中,能够考虑运用Stream及filter。

6

总结

咱们运用的增强for循环,其实是Java供给的语法糖,其完成原理是凭借Iterator进行元素的遍历。

可是假如在遍历过程中,不经过Iterator,而是经过调集类本身的办法对调集进行增加/删去操作。那么在Iterator进行下一次的遍历时,经检测发现有一次调集的修正操作并未经过本身进行,那么可能是发作了并发被其他线程履行的,这时分就会抛出反常,来提示用户可能发作了并发修正,这便是所谓的fail-fast机制。

当然仍是有许多种办法能够处理这类问题的。比方运用一般for循环、运用Iterator进行元素删去、运用Stream的filter、运用fail-safe的类等。

好啦,以上便是本文的全部内容。首要介绍了阿里巴巴Java开发手册制止在foreach循环体中进行元素的add/remove等原因及背面原理。

IT大咖说 | 关于版权

由“IT大咖说(ID:itdakashuo)”原创的文章,转载时请注明作者、出处及微信大众号。投稿、约稿、转载请加微信:ITDKS10(补白:投稿),茉莉小姐姐会及时与您联络!

感谢您对IT大咖说的热心支撑!

相关引荐


引荐文章

  • React Native 在卖菜公司的落地之路

  • 事务高速增加,途牛旅行体系架构的优化实践

  • DevOps与传统的交融落地实践及事例共享


最近活动

  • 深圳南山聊代码?搞点Flink,实时核算才是程序员王道!

点击【阅览原文】更多IT技术圈干货等你发掘

雷火电竞版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

最近发表

    雷火电竞登录_雷火网站_雷火竞猜

    http://www.gogo-design.com/

    |

    Powered By

    使用手机软件扫描微信二维码

    关注我们可获取更多热点资讯

    雷火电竞出品