Java 逆引き集 | コレクション間の差分(removeAll, retainAll) — 差分集計

Java Java
スポンサーリンク

コレクション間の差分(removeAll, retainAll) — 差分集計

差分を取りたいときの定番が removeAll(差集合の除去)と retainAll(共通部分の保持)。どちらも「破壊的(元のコレクションを直接変更)」なので、用途に合わせて安全に使い分けます。セット演算の直感(差・積)で捉えると迷いません。


基本の考え方と違い

  • removeAll(差集合の除去):
    • 意味: this から other に含まれる要素を全部削る → 結果は「this − other」。
    • 戻り値: 変更があれば true。なければ false。
    • 注意: 元を直接変更する(破壊的)。コピーが必要なら new ArrayList<>(list) してから使う。
  • retainAll(共通部分の保持):
    • 意味: this を「other に含まれる要素だけ」にする → 結果は「this ∩ other」。
    • 戻り値: 変更があれば true。
    • 注意: 差分ではなく「交差の抽出」。フィルタを自前で書くより簡潔・高速。
  • equals に依存:
    • ポイント: 要素一致は equals 判定。独自型は equals/hashCode を適切に実装すること。

すぐ試せる基本例

差集合(A − B)を作る(removeAll)

import java.util.*;

List<Integer> A = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> B = List.of(2, 4);

// AからBの要素を削る → 差集合
A.removeAll(B);
System.out.println(A); // [1, 3, 5]
Java
  • ねらい: Aにしかない要素を残したいとき。破壊的なので A を保存したいなら A のコピーを作ってから実施。

共通部分(A ∩ B)を抽出(retainAll)

import java.util.*;

List<String> A = new ArrayList<>(List.of("aaa", "bbb", "ccc"));
List<String> B = List.of("aaa", "eee");

// Aを「Bにある要素だけ」にする → 交差
A.retainAll(B);
System.out.println(A); // [aaa]
Java
  • ねらい: 2集合の重なりだけ欲しいとき。ループ+containsより短く、安全。

例題で身につける

例題1: 新規ユーザーの抽出(差集合)

List<String> today = new ArrayList<>(List.of("u1","u2","u3","u4"));
List<String> existing = List.of("u2","u4","u5");

// 今日のアクセスから既存ユーザーを除く → 新規のみ
today.removeAll(existing);
System.out.println(today); // [u1, u3]
Java
  • ポイント: 「Aにしかない」を取りたいなら removeAll が直感的。

例題2: 配信対象の絞り込み(交差)

List<String> candidates = new ArrayList<>(List.of("tokyo","osaka","kyoto"));
List<String> subscribed = List.of("tokyo","nagoya");

// 購読者に含まれる都市のみ残す
candidates.retainAll(subscribed);
System.out.println(candidates); // [tokyo]
Java
  • ポイント: retainAll は「フィルタ関数の代わりに使う交差」。

例題3: 左右の差分を両側で取得(対称差の簡易計算)

List<Integer> A = List.of(1,2,3,4);
List<Integer> B = List.of(3,4,5,6);

List<Integer> onlyA = new ArrayList<>(A);
onlyA.removeAll(B);         // [1,2]

List<Integer> onlyB = new ArrayList<>(B);
onlyB.removeAll(A);         // [5,6]

// 対称差
List<Integer> symDiff = new ArrayList<>();
symDiff.addAll(onlyA);
symDiff.addAll(onlyB);
System.out.println(symDiff); // [1, 2, 5, 6]
Java
  • ポイント: 破壊的なのでコピーしてから操作すると安全。

実用レシピとテンプレート

  • 差集合(非破壊で返す)
static <T> List<T> difference(Collection<T> a, Collection<T> b) {
    List<T> result = new ArrayList<>(a);
    result.removeAll(b);
    return result;
}
Java
  • 共通部分(非破壊で返す)
static <T> List<T> intersection(Collection<T> a, Collection<T> b) {
    List<T> result = new ArrayList<>(a);
    result.retainAll(b);
    return result;
}
Java
  • 対称差(A △ B)
static <T> List<T> symmetricDifference(Collection<T> a, Collection<T> b) {
    List<T> left = new ArrayList<>(a); left.removeAll(b);
    List<T> right = new ArrayList<>(b); right.removeAll(a);
    left.addAll(right);
    return left;
}
Java
  • Set で高速に(重複無し前提)
Set<String> A = new HashSet<>(List.of("a","b","c"));
Set<String> B = Set.of("b","d");

// 差: A−B
Set<String> diff = new HashSet<>(A);
diff.removeAll(B);

// 積: A∩B
Set<String> inter = new HashSet<>(A);
inter.retainAll(B);
Java

よくある落とし穴と回避策

  • 破壊的であることを忘れる:
    • 回避: 元を残したいなら必ずコピーしてから removeAll/retainAll。
  • equals 実装不備で一致しない:
    • 回避: 独自型は equals/hashCode を正しく実装。セット演算に必須。
  • List.of / Arrays.asList の変更不可:
    • 回避: 変更が必要なら new ArrayList<>(…) に包む。変更不可リストで呼ぶと例外になりうる。
  • 重複の扱い(List vs Set):
    • 注意: List は重複を保持、removeAll は「一致要素をすべて削除」。重複無視して速くやりたいなら Set 前提で処理。
  • 値削除の副作用順序(values.remove)に類似注意:
    • ヒント: Map の values で削除すると「どのキーが消えるか未定」のように、差分操作の対象コレクションと副作用範囲を意識する。

まとめ

  • removeAll は「A − B」、retainAll は「A ∩ B」。どちらも元のコレクションを変更するため、非破壊で使いたい場合はコピーしてから適用する。独自型は equals/hashCode を整え、重複や性能が気になる場合は Set を選ぶとシンプルかつ高速に差分が取れる。
タイトルとURLをコピーしました