質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.93%

拡張for文の動作がわかりません。

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 5,973

TosuTosu

score 49

以下の拡張for文を利用したループ処理で①と③のコードで実行時エラーが発生します。
エラーが発生する場合と発生しない場合があり、拡張for文の動きがわかりません。
お知恵を貸していただきたい考え投稿させていただきました。

    public static void main(String[] args) {

        // ①のコード(実行時エラー)
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("A");
        list1.add("B");
        list1.add("C");
        for (String str : list1) {
            if ("C".equals(str)) {
                list1.remove(str);
            }else {
                System.out.println(str);
            }
        }
        
        // ②のコード(正常終了)
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("A");
        list2.add("B");
        list2.add("C");
        list2.add("D");
        for (String str : list2) {
            if ("C".equals(str)) {
                list2.remove(str);
            }else {
                System.out.println(str);
            }
        }
        // ③のコード(実行時エラー)
        ArrayList<String> list3 = new ArrayList<>();
        list3.add("A");
        list3.add("B");
        list3.add("C");
        list3.add("D");
        list3.add("E");
        for (String str : list3) {
            if ("C".equals(str)) {
                list3.remove(str);
            }else {
                System.out.println(str);
            }
        }
    }

//実行結果
①の結果
  A
  B
  Exception in thread "main" java.util.ConcurrentModificationException
      at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
      at java.util.ArrayList$Itr.next(ArrayList.java:851)
      at Teratail.main(Teratail.java:11)
②の結果
  A
  B
③の結果
  A
  B
  Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at Teratail.main(Teratail.java:40)

どうぞよろしくお願いいたします。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+4

参考情報を紹介します。

- 拡張for文とIteratorの違いについて https://teratail.com/questions/7085

- 繰り返し処理時にArrayList中の要素を削除してもConcurrentModificationExceptionにならない場合(Java) http://kizaru.hatenadiary.com/entry/2015/09/25/011830
...
繰り返し処理中に要素を削除するようなこと自体が、言語の仕様を満たさない使用法
...




投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

checkベストアンサー

+2

拡張for文には、配列かIterable実装クラスのオブジェクトが指定できます。ListはIterableを親に持つので、拡張for文に使用できます。後者の場合、内部ではIteratorが生成され、ループに使用されます。つまり、今回のコードは次と同じ意味になります。
for(Iterator<String> iterator = list.iterator(); iterator.hasNext(); ){
    String str = iterator.next();
    //処理
}
(ただし、拡張for文の場合はこのiteratorにアクセスする術は存在しない)
この場合、for文の中で同じコレクションを参照するオブジェクトがListとIteratorの2つ存在する状態になります。
このように、Iteratorで反復処理をしている間は、Iterator自身の変更メソッド以外でリストに変更が加えられた場合(Listの方から変更を加えるような場合)にIteratorはできるだけ早く例外を発生させる仕組みになっています。
ArrayListがiterator()メソッドで生成するIterator(例外ログ中のArrayList$Itrがこれ)を調べてみると、hasNext()メソッドは「現在のカーソル位置の数値とリスト総数が同じでない場合trueを返す」という実装になっています。next()やremove()の操作の時に、もとのリストでIteratorが想定していない変更が加えられていた場合に、例外を発生させる仕組みになっています(例外ログのcheckForComodification)。
1と3のコードはリスト操作の結果、カーソル位置とリスト総数が一致しないためループが続行し、next()を呼んだ時に例外が発生しますが、2の場合はリスト操作の結果、たまたまカーソル位置とリスト総数が一致したため、次のループに入らずに、next()も呼ばれないために、例外が発生していないのです。

過去に同じような質問をしている方がいますのでこちらもご参照ください。
ConcurrentModificationExceptionが発生するパターンについて

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/11/05 03:38

    お返事が遅れ申し訳ありません。しっかり読ませていただきました。
    少し時間がかかりました。

    拡張for文を展開?で具体的なイメージがつきました。そうなるとコレクションを参照するオブジェクトはListとIteratorの2つで片方(List)に変更があると例外発生となるのですね。
    他にもhasNext()、next()、remove()の挙動についても詳しく説明してくださり、今回の現象をつかむことができました。 やってはいけない操作とだけ考えていたので、深く理解することができました。

    解答してくださった皆様方にもこちらの文章で恐縮ですがありがとうございます。
    どうもありがとうございました!

    キャンセル

0

まず、実装修正から(①のみ)。

        // ①のコード
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("A");
        list1.add("B");
        list1.add("C");

        list1.remove("C");
        for (String str : list1) {
             System.out.println(str);
        }
※list1.remove("C")は、list1内でCという要素を最初の1つのみ削除します。リファレンス
複数存在する可能性があれば、全て削除する実装が必要になります。

拡張for文の内部的な仕組みはわからないけど、
例外を調べてみると、

この例外は、オブジェクトの並行変更を検出したメソッドによって、そのような変更が許可されていない場合にスローされます。

たとえば、あるスレッドが Collection で繰り返し処理を行なっている間に、別のスレッドがその Collection を変更することは一般に許可されません。通常、そのような環境では、繰り返し処理の結果は保証されません。いくつかの反復子 (Iterator) の実装 (JRE が提供するすべての一般的な目的のコレクションの実装の、反復子の実装を含む) は、その動作が検出された場合にこの例外をスローすることを選択できます。この例外をスローする反復子は、「フェイルファスト」反復子と呼ばれます。 反復子は、将来の予測できない時点において予測できない動作が発生する危険を回避するために、ただちにかつ手際よく例外をスローします。

この例外は、オブジェクトが「別の」スレッドによって並行して更新されていないことを必ずしも示しているわけではありません。単一のスレッドが、オブジェクトの規約に違反する一連のメソッドを発行した場合、オブジェクトはこの例外をスローします。たとえば、フェイルファスト反復子を持つコレクションの繰り返し処理を行いながら、スレッドがコレクションを直接修正する場合、反復子はこの例外をスローします。  

通常、非同期の並行変更がある場合、確かな保証を行うことは不可能なので、フェイルファストの動作を保証することはできません。フェイルファストオペレーションは最善努力原則に基づき、ConcurrentModificationException をスローします。したがって、正確を期すためにこの例外に依存するプログラムを書くことは誤りです。ConcurrentModificationException は、バグを検出するためにのみ使用してください。

とあります。
Javaは起動時に必ず1スレッド作られて、mainが実行されるので、
恐らくlist1.remove(str)の際に、別スレッドが立ち上がってるんじゃないかと思います。

②が正常終了できているという認識なようですが、Dが出力されていないのは、正常に終わってないです。
むしろ、例外が出ていないことが不気味です。

更に際しいことはこちらが参考になると思いますが、
つまるところ、for文のなかでfor文の条件要素数を変更することは止めたほうが良いです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.93%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る