HOWTO · Java
Java でリストをチャンクに分割する
このチュートリアルでは、Java でリストを複数の同じサイズのチャンクに分割する方法を示します。
このページの内容
依存ライブラリを追加せずに Java のリストを固定サイズのチャンクに分割するには、チャンクのサイズずつ位置を進め、各範囲を List.subList() で取得します。ストリームのコレクター、Apache Commons Collections、Guava も、必要な結果の形やプロジェクトにすでに含まれている依存関係に応じて選べます。 (ListUtils.partition())
List.subList() を使って Java のリストをチャンクに分割する
List.subList(fromIndex, toIndex) はリストの範囲をビューとして返します。開始位置は含まれ、終了位置は含まれません。チャンクのサイズずつ開始位置を進め、終了位置をリストの長さ以下にすれば、最後のチャンクを短くできます。この例では8個の値を3個ずつに分割します。インデックスを追うと、ループが処理する範囲を確認できます。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ChunkExample {
public static void main(String[] args) {
List<Integer> items = Arrays.asList(10, 20, 30, 40, 50, 60, 70, 80);
int chunkSize = 3;
List<List<Integer>> chunks = new ArrayList<>();
for (int start = 0; start < items.size(); start += chunkSize) {
int end = Math.min(start + chunkSize, items.size());
chunks.add(items.subList(start, end));
}
System.out.println(chunks);
}
}
開始位置は 0、3、6 で、最後の終了位置はリストの長さを超えないようにしています。これにより、すべての値を含めながら、最後の範囲だけ短くできます。入力要素がなければ、ループはチャンクを追加しません。リスト内を進めるため、チャンクのサイズは正の値にしてください。
subList() の結果は独立したコピーではなく、元のリストを参照したままです。変更を元のリストにも反映させたい場合は、チャンクをビューとして扱ってください。ビューの外で元のリストの構造を変更すると、その後のサブリストの使用が不安定になることがあります。独立したチャンクのリストを作るには、たとえば追加時に new ArrayList<>(items.subList(start, end)) のように各範囲をコピーします。
Java Streams を使って Java のリストをチャンクに分割する
ストリームで開始インデックスを生成し、各範囲をサブリストに対応付けられます。提示されている形式の1つでは、IntStream.iterate(0, i -> i < items.size(), i -> i + chunkSize) を使ってインデックスを生成します。もう1つはリストの長さとチャンクのサイズから範囲の数を求め、各インデックス i を i * chunkSize から始まる範囲に対応付けます。どちらも前のループと同じ範囲になります。ストリームを使う場合の説明として、この方法はこのセクションで扱います。
どちらの形式でも、入力要素がなければ範囲の数は0です。また、最後のインデックスを求める前に、チャンクのサイズが正であることを確認してください。これらのストリーム形式について Java の実行結果を示す根拠は提供されていないため、ここではテスト済みの結果ではなく、API と算術に基づいてインデックスの動きを説明しています。境界を一か所で明示したい場合は、直接ループする方法のほうが調整しやすいでしょう。
Collectors.groupingBy() を使って Java のリストをチャンクに分割する
Collectors.groupingBy() は、分類キーに基づいてストリームの要素をグループ化します。チャンクを作るには、要素の値ではなく、要素の位置をチャンクのサイズで割った値で分類します。0から始まるインデックスでは、0 から chunkSize - 1 までが同じキーになり、次の範囲には次のキーが割り当てられます。
たとえば、インデックスをストリームにして Collectors.groupingBy(i -> i / chunkSize) でグループ化し、各グループのインデックスを items.get(i) で値に対応付けます。結果は、チャンク番号をキー、値のリストを値とするマップです。リストのリストが必要なら、マップの値をリストに集めます。選んだマップ型とその順序が実装で定められていない限り、マップの反復順序には依存しないでください。他の方法と同様に、チャンクのサイズを正の値にし、範囲の数を計算する前に入力要素がない場合も考慮します。
Apache Commons Collections を使って Java のリストをチャンクに分割する
Apache Commons Collections がすでにプロジェクトの依存関係にある場合は、リストとチャンクのサイズを引数に ListUtils.partition(items, chunkSize) と呼び出して分割できます。この API を使う前に、ビルド設定へライブラリを追加してください。これは JDK に含まれていません。依存関係の座標や API の詳細は、プロジェクトで選択したバージョンを確認してください。
ここではライブラリのバージョン、依存関係の実行環境、実行結果を示す根拠が提供されていません。そのため、このメソッドを紹介するにとどめ、返される分割結果がビューかコピーか、変更可能か、またはバージョン固有の挙動がどうなるかは断定しません。そうした点が重要な場合は、ビルドで使う Commons Collections の正確なバージョンのドキュメントを確認してください。
Google Guava を使って Java のリストをチャンクに分割する
Guava には Lists.partition() メソッドがあり、リストとチャンクのサイズを渡して Lists.partition(items, chunkSize) のように呼び出します。アプリケーションに Guava がすでに含まれている場合に選べる依存ライブラリの方法です。含まれていない場合は、コレクション API をインポートする前に、プロジェクトの依存関係管理ツールでライブラリを追加してください。
ここでは Guava のバージョンも実行可能な依存関係環境も提供されていないため、このメソッドをテスト済みの例としては示していません。API やアプリケーションが依存する挙動は、プロジェクトで選択した Guava のバージョンに照らして確認してください。メソッド名だけでは、特定のバージョンにおけるコピーかビューか、変更可能性、順序の保証は判断できません。
まとめ
JDK だけで解決でき、範囲を明示的に扱いたい場合は、List.subList() を使うインデックスループを選びます。周囲の処理パイプラインでインデックスによる対応付けが適しているならストリームを、チャンク番号をキーにしたマップが便利なら Collectors.groupingBy() を使います。Apache Commons Collections と Guava は、それらの依存関係がすでにプロジェクトにある場合に簡潔な選択肢になります。どの方法でも、チャンクのサイズを正の値にし、最後に短いチャンクができることを考慮してください。また、ビューやコレクションの挙動は、使用する API と依存ライブラリのバージョンに基づいて確認します。