Open1

【Java】Iteratorメソッド/ループ時にコレクション要素を安全に削除する方法

no215no215

概要

イテレータを使うことでコレクションの要素にカーソルを当てるイメージで扱うことができる。

【課題と目的】ループ中にコレクションの指定要素を削除したい

コレクション要素をfor文や拡張for文のループ処理で削除しようとすると、前詰めされて適切に要素を削除することができなかったり、エラー(ConcurrentModificationExceptionなど)の原因になる。これを適切に削除できるようにするための方法の備忘録です。

【解決方法】Iteratorの使用

Iterator の主な役割は、コレクションの要素にアクセスしながら変更が可能であること。つまりループ中に要素を削除したり、変更を伴う処理が必要な場面で役立ちます。

Iteratorの基本メソッド

  1. hasNext(): 次の要素が存在するかを確認する。要素が存在すれば true を返し、存在しなければ false を返す。

    boolean hasNext();
    
  2. next(): 次の要素にアクセスし、要素を返す。hasNext() で次の要素が存在するかを確認してから使用するのが一般的。

    E next();
    
  3. remove(): 現在の要素をコレクションから削除する。next() を呼び出した直後に remove() を使うことで、現在の要素を安全に削除することができる。

    void remove();
    

【ケーススタディ】

「プレイヤーが何人かいるケースで条件に合致したプレイヤーをコレクションから除外する」みたいなケースを想定してみる。条件は本質ではないため内容を簡易にするために非現実的な超ざっくりした条件になってます。

【例】ターン指定のケース
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> players = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

    int N = 5;

    ///////////////////
    // Nターンのケース //
    ///////////////////
    Iterator<Integer> it = players.iterator(); // <- イテレータ(リスト要素にカーソルを当てるイメージ)を取得
    for (int i = 0; i < N; i++) {
      while(it.hasNext()) { // 次の要素があるか判定しtrue or falseで返す
        int num = it.next(); // カーソルを次に進めるイメージ(要素を変数に代入しているが代入しなくてもOK)
        // なんらかの条件: 【例】偶数番号をplayersリストから取り除く(この例だとfor文でNターン指定してる意味ないけど)
        if (num % 2 == 0) {
          System.out.println(String.format("プレイヤー%dを除外しました", num));
          it.remove();
        }
      }
    }
    // 残りのプレイヤーを表示
    players.stream().forEach(System.out::println);
  }
}

この例だと5ターン指定している意味がないけど、適切な条件(負けフラグがついているプレイヤーなど)を指定すればターン指定の意味がある。

コンソール
プレイヤー2を除外しました
プレイヤー4を除外しました
プレイヤー6を除外しました
プレイヤー8を除外しました
プレイヤー10を除外しました
1
3
5
7
9
【例】回数指定のケース
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> players = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

    int N = 5;


    ////////////////
    // N回のケース //
    ////////////////
    int count = 0;
    Iterator<Integer> it = players.iterator();
    while(it.hasNext() && count < N) {
      int num = it.next();
      // なんらかの条件: 【例】偶数番号をplayersリストから取り除く
      if (num % 2 == 0) {
        System.out.println(String.format("プレイヤー%dを除外しました", num));
        it.remove();
      }
      count++;
    }

    // 残りのプレイヤーを表示
    players.forEach(System.out::println);
  }
}

条件で5回指定しているので「6」以降はifのチェックが無視される。
アルゴリズム問題とかだとこっちの方が多いかな?

コンソール
プレイヤー2を除外しました
プレイヤー4を除外しました
1
3
5
6
7
8
9
10