アリババは除く。foreachループ素子に禁止されている理由[ジャワ] /操作を追加

アリババは除く。foreachループ素子に禁止されている理由[ジャワ] /操作を追加

foreachループ(foreachループ)は、典型的な素子のアレイ又はセットをループするために使用される制御フローのコンピュータプログラミング言語ステートメントです。

アリババJava開発者のためのハンドブックには、そのような条項があります:

アリババは削除でforeachループの要素で禁止されているのはなぜ/操作を追加

しかし、マニュアルでは、我々は深くその規定の考え方を分析するために来て、具体的な理由を与えるものではありません。

1

foreachループ

foreachループ(foreachループ)は、典型的な素子のアレイ又はセットをループするために使用される制御フローのコンピュータプログラミング言語ステートメントです。

Java言語、foreachループの導入の当初からJDKの1.5.0。配列、コレクションの側面を介して、foreachのは、開発者にとって非常に便利を提供しています。また、一般的に拡張forループと呼ばれます。

次のようにforeachの構文は次のとおりです。

次の例では、foreachループとリサイクルのための共通の証明します:

出力コードが実行結果です。

あなたが同じ効果を再生することができ、コレクションまたは配列構文を通じてforeachループを使用して見て、一般的な循環、およびコードより簡潔用することができます。だから、foreachループはまた、一般的に拡張forループと呼ばれます。

しかし、資格のプログラマーとして、我々は唯一の拡張forループされているものを知ってはいけない、あなたはまた、原則的には拡張forループされているものを知っている必要がありますか?

実際には、強化されたループは、上記のコードの後に​​クラスファイルが(JAD使用ツール)をコンパイルするためにコンパイルされている場合、あなたは以下のコードを取得することができ、Java用のシンタックスシュガーはをご提供しています:

その拡張forループ、実際にはwhileループに依存し、イテレータの実装を見つけることがあります。(この実装は、後に使用されることに注意してください!)

2

問題を再現するために、

仕様の状態は、私たちは、foreachループ内の要素の集合に対して操作を追加/削除することはできませんし、我々はそれをしようと何が起こるか参照してください。

4つの文字列、それぞれ、ホリス、ホリス、HollisChuang及びHを含有する上記のコード、リストを確立し、初期化する最初の二重括弧構文(二重括弧構文)

そして、ホリス要素に等しいリスト要素の内容を削除し、リストを横断するループのための通常のを使用しています。次のように出力リスト、出力結果は以下のとおりです。

[ホリス、HollisChuang、H]

これらは、横断しながら、削除するループのために一般的に使用され、その後、私たちは見て、拡張forループを使用している場合、その後、何が起こります:

上記のコード、要素をループの拡張を使用して、そこにホリス列要素を削除しようとします。次の例外がスローされます上記のコードを実行します。

java.util.ConcurrentModificationExceptionが

同様に、読者がaddメソッドを使用して拡張forループ内の要素を追加しようとすることができ、同じ結果がスローされます。

--fail高速のJavaコレクションエラー検出メカニズムをトリガーするため、この異常の原因が発生します。

3

フェイルファスト

次に、我々は要素を追加/削除する拡張forループ時の理由を分析する必要があることはjava.util.ConcurrentModificationExceptionが、そうでフェイルファストバイナリ、フェイルファスト原理とは何であるかである最後の次の説明をスローします。

フェイルファスト、つまり高速で失敗し、それがJavaのエラー検出機構の集まりです。構造を変更する動作のねじ(非フェイルセーフコレクションクラス)の複数のセットは、フェイルファスト機構を有することができる場合、それがスローされたときにConcurrentModificationException(同時修飾とき検出された物体、しかし、異常のうちの)そのような変更を許可していません。

また、シングルスレッドのルールに違反している場合ではなく、マルチスレッド環境であれば、ということに注意し、また、例外をスローに変更する可能性があります。

まあ、拡張forループ元素の削除は、行う方法の規則に違反しているのですか?

この質問を分析するために、我々は最初のループのために、このシンタックスシュガーの解凍砂糖(クラスのJADファイルにコンパイル逆コンパイルの使用を)強化され、次のコードを与えます:

そして、上記のコードを実行し、それが例外をスローします。私たちは、ConcurrentModificationExceptionがの完全なスタックを見てみましょう。

アリババは削除でforeachループの要素で禁止されているのはなぜ/操作を追加

例外スタックすることにより、我々は、ライン23コールForEachDemo例外が発生したチェーン、に行くことができます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方法核心逻辑如下:

アリババは削除でforeachループの要素で禁止されているのはなぜ/操作を追加

可以看到,它只修改了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的值。那么就不会再抛出异常了。其实现代码如下:

アリババは削除でforeachループの要素で禁止されているのはなぜ/操作を追加

3、使用Java 8中提供的filter过滤

Java 8中可以把集合转换成流,对于流有一种filter操作, 可以对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

4、直接使用fail-safe的集合类

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

由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。

コピーコンテンツはConcurrentModificationExceptionを回避する利点に基づいており、これもまた、イテレータが変更されたコンテンツへのアクセス権を持っていない、すなわち:イテレータトラバーサルは変更が起こるの元のセットの横断中、一瞬のコピーを取得するためにコレクションを開始しますイテレータは知られていません。

コンテナはjava.util.concurrentパッケージが失敗したの下で、あなたはマルチスレッドの同時実行、同時変更を使用することができ安全です。

5、実際には拡張forループを使用すること

我々は非常にセットして決定されている場合は、要素を削除しようとして一つは、このようなSet操作用として、そう実際に続行されません、すぐにループを終了し、長い削除した後、拡張forループを使用することができ、唯一の単語が含まれていますそれを横切るように、それは次の次の方法に実行をコーディングすることはありません。

これは、例外をスロー避けるために、フェイルファストメカニズムをトリガする以上の5つの方法を回避することができます。同時実行のシナリオ場合は、それはシングルスレッドのシナリオ、前のコードJava8であれば、コンテナ同時パッケージを使用することをお勧めします、イテレータの要素の削除、Java8と更新されたバージョンを使用することをお勧めします、あなたはストリームとフィルタの使用を検討することができます。

6

概要

私たちは、拡張forループを使用して、実際には、その実装原理が援助イテレータの要素をトラバースすることで、Javaのが提供するシンタックスシュガーです。

しかし、横断中に、ではないイテレータによる場合は、しかし、クラス自体の収集方法でコレクションを削除/追加します。イテレータでの横断は、テストされ、セットはそれ自体ではなく、一度の操作を変更することが見出されているときに、例外にこの時間をスローします他のスレッドを並行して起こって実行することができる、ユーザーが発生するように指示されましたいわゆるフェイルファストメカニズムで同時変更、。

もちろん、これらの問題を解決するために多くの方法があります。例えば、イテレータ要素の除去を使用して、ストリーム・フィルタの使用を通常のサイクルを使用して、の使用は、フェイルセーフ等を挙げることができます。

おすすめ

転載: www.cnblogs.com/kise-ryota/p/11238628.html