導入
システム開発において、「2つのリストの共通要素だけを取り出したい」という場面は頻繁に発生します。例えば、ユーザーが選択したタグと、システム側で有効なタグの突き合わせなどがこれに該当します。Javaのコレクションフレームワークには、これを一行で実現する「retainAll」という強力なメソッドが用意されています。このメソッドを知っているだけで、ループ処理を駆逐し、可読性の高いコードを書くことができます。
基礎知識
「積集合(Intersection)」とは、集合論において「両方の集合に共通して含まれる要素」のことです。JavaのCollectionインターフェース(ListやSetなど)が持つretainAllメソッドは、呼び出し元のコレクションから、「引数で渡したコレクションに含まれない要素」をすべて削除し、結果として共通部分(積集合)だけを残すという動作をします。
注意すべき点は、このメソッドが破壊的(破壊的変更)であることです。呼び出し元のリストそのものが書き換わるため、元のデータを保持しておきたい場合は、処理前にコピーを作成する必要があります。
実装/解決策
基本的な手順は以下の通りです。
1. 処理対象のコレクションAと、比較対象のコレクションBを用意する。
2. Aが変更されても問題ないか確認する(問題があればAをコピーする)。
3. A.retainAll(B)を実行する。
このメソッドは、Setに対して使うと非常に高速に動作します。Listに対して使う場合は、要素数に応じて計算量が増えるため、大規模データを扱う際は注意が必要です。
サンプルプログラム
以下のコードは、2つのリストから共通の要素を抽出する例です。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class RetainAllSample {
public static void main(String[] args) {
// 元となるリスト
List
List
// listAをコピーして、元のリストを保護する(重要!)
List
// retainAllを実行して積集合を抽出
// listBに含まれない要素がcommonListから削除される
boolean isChanged = commonList.retainAll(listB);
// 結果の出力
System.out.println(“共通要素: ” + commonList); // [Java, Go]
System.out.println(“変更があったか: ” + isChanged);
}
}
応用・注意点
現場で活用する際の重要なポイントを3つお伝えします。
1. パフォーマンスの最適化: 比較対象となるコレクション(引数側)がListの場合、要素数が増えると検索効率が悪化します。比較対象を事前にHashSetに変換してからretainAllを呼ぶと、計算量がO(N M)からO(N)に改善されるため、大規模データでは必須のテクニックです。
2. nullの扱い: retainAllにnullを渡すとNullPointerExceptionが発生します。また、コレクション内にnullが含まれている場合、環境によって動作が異なる可能性があるため、事前にnullチェックを入れるのが安全です。
3. Stream APIとの使い分け: もし元のリストを破壊したくない、かつ処理を関数型スタイルで記述したい場合は、Stream APIのfilterを使う方が現代的です。
例: listA.stream().filter(listB::contains).toList();
状況に応じて、「破壊的変更で効率を優先するretainAll」か、「イミュータブルな設計を優先するStream API」かを選択してください。

コメント