本ページはプロモーションが含まれています

Java入門

Java重複チェックの方法|List・Set・Map4パターン比較

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
10枚
Duplicate(List・Set・Mapで重複チェック)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Javaの重複チェックは、単純な排除なら「HashSet」、順序を保つなら「LinkedHashSet」、出現回数を数えるなら「HashMap」、他の処理とつなげるなら「Stream APIのdistinct()」を選びます。

重複があるかどうかだけを知りたいなら、new HashSet<>(list).size() != list.size() の1行で判定できます。

Listのcontainsを使う方法や複数キーでの重複判定、自作クラスのequals/hashCodeまで、12年Javaの現場で書いてきたコードをもとに整理しました。

この記事でわかること

  • List・Set・Map・Stream APIによる4つの重複チェックパターンと使い分け
  • 重複の有無を1行で判定する方法と、複数キーでの重複チェック
  • 自作クラス(オブジェクト)でequals/hashCodeが必要な理由と正しい実装方法
  • 大規模データでのメモリ管理・並列処理・DB活用の注意点

PARTS LIST /目次

Javaの重複チェックとは?

重複チェックとは、データの中に「同じものが混ざっていないか」を確認して、見つかったら取り除いたりエラーとして扱ったりする作業のことです。

重複したデータが入り込むとシステムの動きが変になったり、集計結果がズレたりするため、重複チェックはデータの信頼性を保つために欠かせない工程です。

重複チェックが必要になるよくあるケース

開発現場で重複チェックが必要になる代表的な場面は、次の3つです。

  • ユーザー登録時のメールアドレス確認:すでに登録されているメールアドレスで、別の人がアカウントを作れないように制限をかける
  • CSVファイルの取り込み:外部システムから連携されたデータに、同じIDの商品が含まれていないか検証
  • キャンペーンの応募:1人のユーザーが何度も応募できないよう、IDで制限をかける

メールアドレス確認・CSV重複検証・応募制限といった処理を適切に実装しないと、データ不整合という重大なバグにつながります。

Javaで重複チェックするときの基本的な考え方

重複を判定するための基準は「何をもって同じとみなすか」です。

プリミティブ型(intやdoubleなど)であれば数値が同じかどうかで判断しますが、オブジェクトの場合は比較のルールを自分で定義する必要があります。

IDが同じなら同じとみなすのか、すべてのフィールドが一致して初めて重複とみなすのか、設計段階で決める必要があります。

Javaでは、この判定にequalsメソッドを利用します。

Javaで重複チェックする4つの方法

Javaには重複をチェックするための手段がいくつか用意されています。

もっとも効率的で推奨されるのは「Set」を使う方法です。

Setは「重複を許さない」データ構造なので、要素を追加するだけで重複が自動的に消えます。

しかし、要件によってはListやStream APIを使うべき場面もあります。

手法

  1. List(リスト): 手動でループを回して確認する
  2. Set(セット): コレクションの特性を利用して排除する
  3. Map(マップ): 出現回数をカウントして判定する
  4. Stream API: Java 8以降で使える関数型スタイルで処理する
方法用途速度順序保持重複カウント
HashSet単純な重複排除◎××
LinkedHashSet順序を保った重複排除○○×
Stream.distinct()関数型スタイルで重複排除○○×
HashMap重複の出現回数を集計◎×○

それぞれの特徴を理解したうえで、データ量・順序保持の要否・集計の必要性に応じて使い分けます。

重複チェックの4つの方法(何に入れて、どう判定するか)
METHOD / 方法
やり方
List
contains や frequency で数える手軽だが要素が多いと遅くなる
Set
重複を許さない集合に入れるHashSet/LinkedHashSet/TreeSet
Map
キーごとの出現回数を数える重複した要素の抽出にも使える
Stream
distinct() で除去groupingBy で数える

Listで重複チェックする方法

Listを使った重複チェックは仕組みの理解には役立ちますが、データ量が増えると処理速度が極端に落ちるため、大量データの処理には向きません。

数件から数十件程度のデータであれば問題なく動作します。

あわせて読む

containsを使うシンプルな方法

新しいリストを用意し、そこに要素が存在しない場合のみ追加していく手法です。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ListDuplicateCheck {
    public static void main(String[] args) {
        List<String> originalList = Arrays.asList("りんご", "みかん", "りんご", "バナナ");
        List<String> uniqueList = new ArrayList<>();

        for (String item : originalList) {
            // リストに含まれていない場合のみ追加
            if (!uniqueList.contains(item)) {
                uniqueList.add(item);
            }
        }

        System.out.println(uniqueList); 
        // 結果: [りんご, みかん, バナナ]
    }
}

containsを使うこのコードは直感的でわかりやすいのがメリットです。

しかし、containsメソッドは内部でリストの全要素を走査するため、データ数が増えると処理時間が長くなります。

あわせて読む

Collections.frequencyを使った方法

Collections.frequencyを使うと、指定した要素がリスト内にいくつ存在するかを数えられます。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class FrequencyCheck {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("A", "B", "A", "C");

        for (String item : items) {
            // 出現回数が2回以上のものを探す
            if (Collections.frequency(items, item) > 1) {
                System.out.println(item + " は重複しています");
            }
        }
    }
}

重複している要素そのものを特定したい場合に便利です。

ただし、Collections.frequencyも内部的に全探索を行うため、データ量が増えるとパフォーマンスは低下します。

binarySearchで重複を確認する

ここまでのcontainsやfrequencyは、リストの先頭から末尾まで全走査するため計算量はO(n)です。

しかし、リストがあらかじめソート済みであれば、Collections.binarySearchを使うことで計算量をO(log n)まで落とせます。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class BinarySearchCheck {
    public static void main(String[] args) {
        // 昇順にソート済みであることが前提条件
        List<Integer> sortedList = Arrays.asList(1, 3, 5, 7, 9);

        int target = 5;
        int index = Collections.binarySearch(sortedList, target);
        boolean exists = index >= 0;

        System.out.println(target + " は存在する: " + exists);
        // 結果: 5 は存在する: true
    }
}

ただし、複数の同値要素がある場合にどのインデックスが返されるかはJDK実装依存のため(Oracle公式ドキュメントで保証外と明記されています)、重複の件数を正確にカウントする用途には向きません。

また、対象のリストが事前にソートされている必要があり、ソートされていないリストに使うと結果は保証されません。

実務では、マスタデータなど事前にソート済みであることが確定しているリストを対象にした「特定の値が含まれているかどうか」の存在確認に限定して使うのが安全です。

日常的な重複排除であれば、後述するHashSetを使う方がシンプルで速く済みます。

Setで重複を削除する実装例

重複チェックにおいて、もっともパフォーマンスが良いのがSetインターフェースを利用する方法です。

HashSetに1つずつaddすると、2回目の"Java"だけ入らない
1
add → trueset.add("Java")新しい要素なので追加。中身:Java
2
add → trueset.add("Python")新しい要素なので追加。中身:Java, Python
3
add → falseset.add("Java")すでにあるので追加されない。同じ値は2つ持てないため add() が false を返し、この時点で「重複あり」と確定できる
4
add → trueset.add("Ruby")新しい要素なので追加。中身:Java, Python, Ruby
5
SIZE / 件数比較new HashSet<>(list).size() != list.size() → trueList 4件 → Set 3件。件数が減った=重複あり
NOTEHashSetの中の並び順は保証されません(順序を保つならLinkedHashSet)。

Setは数学の「集合」をモデルにしており、仕組み上、同じ値を2つ保持できません。

この特性を利用することで、複雑な判定ロジックを書かずに重複を排除できます。

あわせて読む

HashSetで重複を排除する基本パターン

HashSetは、順序を保証しない代わりにもっとも高速に動くSetの実装です。

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        List<String> input = Arrays.asList("Java", "Python", "Java", "Ruby");
        
        // Listをコンストラクタに渡すだけで重複が消える
        Set<String> uniqueSet = new HashSet<>(input);

        System.out.println(uniqueSet);
        // 結果の例: [Java, Ruby, Python] (並び順は保証されない。Java 21で実行した場合)
    }
}

10万件を超えるような大量データを扱う場合、Listでチェックするよりも圧倒的に高速です。

参考までに、すべて異なる値の10万件のString型データで重複排除した場合、List.containsでのループは約5秒かかります。

一方、HashSetへの変換は0.01秒以下で完了します(計測環境: Java 21, MacBook Pro M3)。

特に順序を気にする必要がない場合は、迷わずHashSetを選んでください。

Listに重複があるかだけを判定する方法

重複を取り除くのではなく「重複があるかどうか」を true/false で知りたいだけなら、Setに変換したあとの件数を元のListと比べます。

import java.util.HashSet;
import java.util.List;

public class HasDuplicateExample {
    public static void main(String[] args) {
        List<String> emails = List.of("[email protected]", "[email protected]", "[email protected]");

        // Setに変換して件数が減れば、重複がある
        boolean hasDuplicate = new HashSet<>(emails).size() != emails.size();

        System.out.println(hasDuplicate);
        // 結果: true
    }
}

Setに入れると重複分が消えるため、件数が減れば重複ありと判定できます。

大量データで最初の重複を見つけた時点で処理を止めたい場合は、ループの中で Set.add() の戻り値を確認します。add() は同じ要素がすでにあると false を返すため、その時点で重複ありと確定できます。

LinkedHashSetで順序を保ったまま重複を除去

データの並び順を変えたくない場合は、LinkedHashSetを使用します。

入力された順番を記憶しているため、元のリストの並びを維持したまま重複だけを取り除けます。

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class LinkedHashSetExample {
    public static void main(String[] args) {
        List<String> input = Arrays.asList("東京", "大阪", "東京", "福岡");
        
        Set<String> orderedSet = new LinkedHashSet<>(input);

        System.out.println(orderedSet);
        // 結果: [東京, 大阪, 福岡] (出現順が維持される)
    }
}

UI(画面)に表示するリストなど、ユーザーが見たときの並び順が重要なケースで重宝します。

TreeSetでソートしながら重複チェック

重複を排除しつつ、辞書順や昇順に並べ替えたい場合はTreeSetが最適です。

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 1, 3, 5, 2);
        
        Set<Integer> sortedSet = new TreeSet<>(numbers);

        System.out.println(sortedSet);
        // 結果: [1, 2, 3, 5] (昇順にソートされる)
    }
}

内部でデータを並べ替える処理が入るため、HashSetよりは若干遅くなりますが、ソート処理を別途書く手間が省けます。

Mapで重複をカウント・抽出するにはどうする?

単に重複を取り除くだけでなく、「どのデータが何回重複しているか」を知りたいときはMapを使います。

merge()で1件ずつ数え、最後に「2回以上」だけ取り出す
1
MERGEmerge("A案", 1, Integer::sum) → A案=1初登場なので1
2
MERGEmerge("B案", 1, Integer::sum) → B案=1初登場なので1
3
MERGEmerge("A案", 1, Integer::sum) → A案=2既存の値に+1
4
MERGEmerge("C案", 1, Integer::sum) → C案=1初登場なので1
5
MERGEmerge("A案", 1, Integer::sum) → A案=3既存の値に+1
6
FILTER / 抽出entry.getValue() > 1 のものだけ出力重複データ: A案 (3回)
NOTE入力は votes(List)= A案, B案, A案, C案, A案。countMap はキー=要素、値=出現回数です。

Keyに「データそのもの」、Valueに「出現回数」を持たせるのが定石です。

HashMapで出現回数をカウントする

Java 8以降では、mergeメソッドを使うとカウントアップ処理が簡潔に書けます。

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MapCountExample {
    public static void main(String[] args) {
        List<String> votes = Arrays.asList("A案", "B案", "A案", "C案", "A案");
        Map<String, Integer> countMap = new HashMap<>();

        for (String vote : votes) {
            // キーが存在しなければ1、存在すれば現在の値+1
            countMap.merge(vote, 1, Integer::sum);
        }

        System.out.println(countMap);
        // 結果の例: {C案=1, B案=1, A案=3} (HashMapの並び順は保証されない。Java 21で実行した場合)
    }
}

この方法は、アンケートの集計や、ログファイル内のエラー回数の分析などで頻繁に使用されます。

重複した要素だけ抽出する方法

Mapで集計したあと、Valueが2以上のものだけを取り出せば、重複データリストが作成できます。

for (Map.Entry<String, Integer> entry : countMap.entrySet()) {
    if (entry.getValue() > 1) {
        System.out.println("重複データ: " + entry.getKey() + " (" + entry.getValue() + "回)");
    }
}

エラーチェックのロジックとして、「重複している行をユーザーに知らせる」といった機能要件がある場合に有効です。

あわせて読む

Stream APIで重複を排除する実装例

Java 8(2014年リリース)で登場したStream APIは、コレクション操作を宣言的に記述できる強力なツールです。

複雑な重複チェックロジックも、流れるようなコードで記述できます。

distinct()で重複を取り除く

distinct()はもっとも簡単な重複排除の方法です。

Stream処理の途中で挟み込むことができるため、例えば「フィルタリングしてから重複排除し、最後に大文字に変換する」といった一連の流れをスムーズに書けます。

実務では、distinct()は内部的にHashSetと同等の仕組みで動くため、単体の速度面での優位性はほとんどありません。

それでもStream APIを選ぶ場面は、フィルタリングや変換など他の処理と1本のメソッドチェーンでつなげたいときです。

逆に重複排除だけが目的なら、HashSetへの変換のほうがコードの意図が明確になります。

あわせて読む

Collectors.groupingByで重複を数える

Mapを使ったカウント処理をStreamで書くと、以下のようになります。

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamGroupingExample {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("apple", "banana", "apple");

        Map<String, Long> counts = items.stream()
            .collect(Collectors.groupingBy(
                s -> s, 
                Collectors.counting()
            ));

        System.out.println(counts);
        // 結果: {banana=1, apple=2}
    }
}

SQLのGROUP BY句に近い感覚で操作できるため、データベースに慣れたエンジニアには馴染みやすい書き方です。

Streamで重複要素のみ抽出する手法

少し応用的なテクニックですが、Setに「追加できたかどうか」を判定条件にすることで、重複要素だけを抽出できます。

Set.add()メソッドは、すでに要素が存在する場合にfalseを返す性質を利用します。

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ExtractDuplicates {
    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(1, 2, 3, 1, 4, 2);
        Set<Integer> uniqueSet = new HashSet<>();

        List<Integer> duplicates = nums.stream()
            .filter(n -> !uniqueSet.add(n)) // 追加できなかった=重複
            .collect(Collectors.toList());

        System.out.println(duplicates);
        // 結果: [1, 2]
    }
}

実務では、CSVインポート時に重複行を検出してユーザーに警告を出す処理や、注文データの二重送信チェックなどで使われます。

重複データの行番号(インデックス)を特定する方法

「何番目の要素が重複しているか」を知りたい場合もあります。たとえばCSV取り込み時に「◯行目が重複しています」と伝えるには、値だけでなく元リストのインデックスも必要です。

IntStream.range()で添字を発行し、Set.add()がfalseを返すかどうかで重複判定しながらインデックスを集めます。

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class DuplicateIndexFinder {
    public static void main(String[] args) {
        List<String> rows = List.of("A案", "B案", "A案", "C案", "B案");
        Set<String> seen = new HashSet<>();

        List<Integer> duplicateIndexes = IntStream.range(0, rows.size())
            .filter(i -> !seen.add(rows.get(i))) // 追加できなかった=重複
            .boxed()
            .collect(Collectors.toList());

        System.out.println(duplicateIndexes);
        // 結果: [2, 4](0始まりのインデックス。2行目と4行目が重複)
    }
}

この方法なら、重複しているという事実だけでなく「元データの何番目にあったか」まで特定できるため、CSVインポートのエラーメッセージやログ出力に活用できます。

あわせて読む
あわせて読む

オブジェクト(自作クラス)の重複チェックはどうする?

私が新人だった頃、顧客情報の取り込み処理でequals/hashCodeのオーバーライドを忘れ、同一顧客のデータが二重登録されてしまった経験があります。

同じ値のUserを2回addしたとき、equals/hashCodeの有無で結果が変わる
CODE / 共通userSet.add(new User("田中", 30)); を2回1人目はSetが空なのでそのまま入る。違いが出るのは2人目
オーバーライドなし(Object標準のまま)NG:size() → 2Object標準のequalsは「同じインスタンスか」だけを見る。別々にnewしたので、値が同じでも一致しない。同じ「田中・30歳」が2件残り、重複チェックが効いていない
オーバーライドあり(name・ageで比較)OK:size() → 1hashCode(Objects.hash(name, age))が同じ値になり、equalsでname・ageを比べて一致。2回目のaddはfalseを返し、追加されない

ここまでの例は文字列や数値などの単純なデータ型でしたが、自作のクラス(UserクラスやProductクラスなど)で重複チェックを行う場合は注意が必要です。

equalsメソッドとhashCodeメソッドを必ずオーバーライドする必要があります。

オーバーライドしないと、全く同じ値を持っていても「別のもの」として扱われてしまうからです。

オブジェクトが比較できない理由

Javaのデフォルトの動作では、オブジェクトの比較は「メモリ上のアドレスが同じか」で行われます。

User user1 = new User("田中");
User user2 = new User("田中");

上記のように、中身が同じ「田中」であっても、newを使って別々に生成されたオブジェクトは、アドレスが異なるため「重複していない」と判定されます。

このアドレスの差分による誤判定が、意図しない重複登録の原因となります。

equalsとhashCodeを正しく実装する方法

重複チェックを正しく機能させるには、以下のように定義してください。

  • equals: オブジェクトの中身(フィールドの値)が同じならtrueを返すように書き換える。
  • hashCode: 中身が同じオブジェクトなら、同じハッシュ値を返すように書き換える。

特にHashSetやHashMapは、内部でハッシュコードを使ってデータの格納場所を決めているため、hashCodeの実装を忘れると重複排除が機能しません。

手動で実装する場合は、Java標準クラスのObjects.equals()とObjects.hash()を使うと、null安全な比較とハッシュ値の計算が簡潔に書けます。

import java.util.Objects;

public class User {
private String name;
private int age;

  public User(String name, int age) {
      this.name = name;
      this.age = age;
  }

  @Override
  public boolean equals(Object o) {
      // 同一インスタンスなら即true
      if (this == o) return true;
      // nullまたは型が異なる場合はfalse
      if (o == null || getClass() != o.getClass()) return false;
      User user = (User) o;
      // フィールドの値が同じであれば同一とみなす
      return age == user.age && Objects.equals(name, user.name);
  }

  @Override
  public int hashCode() {
      return Objects.hash(name, age);
  }

}

equalsとhashCodeをオーバーライドすることで、HashSetに追加する際もフィールドの値をもとに重複判定が正しく行われます。

Set<User> userSet = new HashSet<>();
userSet.add(new User("田中", 30));
userSet.add(new User("田中", 30)); // 同じ値なので追加されない

System.out.println(userSet.size()); // 結果: 1

ただし、フィールドが増えるたびにequals/hashCodeのコードを修正し続けるのは手間がかかりますし、修正漏れがバグの温床になります。

データ保持だけが目的のクラスであれば、Java 16以降で正式導入されたrecordを使うことでequals/hashCodeの実装自体が不要になる選択肢もあります。

あわせて読む

lombok @EqualsAndHashCode の使い方

Lombokというライブラリを使えば、この手間を完全に解消できます。

あわせて読む

クラスにアノテーションをつけるだけで、自動的にメソッドを生成してくれます。

あわせて読む
import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class User {
    private String name;
    private int age;
    // コンストラクタやGetterなどは省略
}

Lombokは多くの現場で使われています。開発効率が上がるだけでなく、実装ミスを防ぐことができるからです。

複数キー(複数項目)で重複チェックする方法

「顧客IDと商品IDの組み合わせが同じなら重複」のように、複数の項目をまとめて判定したいケースもあります。

クラス全体のequals/hashCodeを変えずに済ませるなら、判定に使う項目だけを持つrecordをキーとして作り、Setに入れて判定します。

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MultiKeyDuplicateExample {
    // 注文データ(数量は重複判定に使わない)
    record Order(String customerId, String productId, int quantity) {}

    // 重複判定に使う2項目だけを持つキー
    record OrderKey(String customerId, String productId) {}

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("C001", "P100", 1),
                new Order("C002", "P100", 2),
                new Order("C001", "P100", 3));

        Set<OrderKey> seen = new HashSet<>();
        // 顧客IDと商品IDの組み合わせが2回目以降の注文だけを取り出す
        List<Order> duplicates = orders.stream()
                .filter(o -> !seen.add(new OrderKey(o.customerId(), o.productId())))
                .toList();

        System.out.println(duplicates);
        // 結果: [Order[customerId=C001, productId=P100, quantity=3]]
    }
}

recordはequals/hashCodeが自動生成されるため、2項目が一致するキーは同じ要素として扱われます。数量のような判定に使わない項目は、キーに含めなければ無視されます。

Java 16より前の環境では、customerId + ":" + productId のように区切り文字付きで連結した文字列をキーにする方法でも代用できます。

大規模データにおけるJava重複チェックの注意点

データ量が数万件程度ならメモリ上で処理しても問題ありませんが、数百万、数千万件となるとメモリ管理の工夫が必要です。

安易にListに入れて処理すると、OutOfMemoryErrorでアプリケーションが停止します。

メモリ使用量を抑えるための設計

大量データを扱う際は、すべてのデータを一度にメモリに読み込まない工夫が必要です。

たとえばファイルを1行ずつ読みながら処理する場合は、1行ごとにDBへ問い合わせて重複を確認します。

メモリを節約したいなら、Bloom Filterのような確率的データ構造で判定する方法もあります。

Bloom Filterは「含まれていない」は100%正確に判定でき、「含まれている」は偽陽性(false positive)が発生しうるデータ構造です。

Java向けにはGoogle GuavaライブラリのBloomFilterクラスが手軽に使えます。

並列処理(parallelStream)使用時の注意

Java 8以降のparallelStreamを使えば、マルチコアCPUを活かして処理を高速化できます。

ただし、重複チェックで並列処理を行う場合は、スレッドセーフなクラスを選びます。

通常のArrayListやHashSetはスレッドセーフではないため、並列処理中に同時に書き込みを行うとデータが壊れます。

ConcurrentHashMapのキーセットを利用するなど、並行処理に対応したクラスを選定してください。

外部データベースを使った重複チェック戦略

Javaプログラム側ですべて処理しようとせず、データベース(RDBMS)の機能を活用するのも賢い選択です。

SQLのDISTINCTキーワードを使えば、DB側で重複を排除した結果だけを取得できます。

テーブルの特定カラムにUNIQUE制約(ユニークインデックス)を貼っておくと、重複データをINSERTした瞬間にDBがエラーを返します。

Java側のチェックをすり抜けたデータも止められるため、もっとも確実なガードです。

Java重複チェックに関するよくある質問

Q1. Javaで一番簡単に重複チェックする方法は?

A1. HashSetを使うのがもっとも簡単です。addメソッドで要素を追加するだけで、Setの性質上、自動的に重複が排除されます。ループで手動比較する必要がなく、コード量も最小限で済みます。

Q2. Listの重複チェックとSetの重複チェックの違いは?

A2. Listはデータ構造自体が重複を許すため、containsメソッドやループで手動チェックする必要があります。一方Setは重複を許さないデータ構造のため、追加するだけで自動的に重複が排除されます。処理速度もSet(特にHashSet)の方が高速です。

Q3. 自作クラス(オブジェクト)の重複チェックで注意すべきことは?

A3. equalsメソッドとhashCodeメソッドを必ずオーバーライドする必要があります。オーバーライドしないと、Javaはオブジェクトをメモリ上のアドレスで比較するため、中身が同じでも「別のもの」として扱われ、重複チェックが正しく機能しません。

Q4. 重複の出現回数をカウントするにはどうする?

A4. HashMapを使い、要素をキー、出現回数を値として集計します。Stream APIを使う場合はCollectors.groupingByとCollectors.counting()を組み合わせることで、同様の集計を簡潔に書けます。

Q5. 大量データ(数百万件以上)で重複チェックするときの注意点は?

A5. すべてのデータを一度にメモリへ読み込むとOutOfMemoryErrorのリスクがあります。Bloom Filterのような確率的データ構造を使うか、データベース側のUNIQUE制約やDISTINCTキーワードで重複排除をDBに任せる設計が有効です。

Q6. LombokでequalsとhashCodeを自動生成できる?

A6. できます。クラスに@EqualsAndHashCodeアノテーションを付けるだけで、LombokがequalsとhashCodeメソッドをコンパイル時に自動生成します。手動実装のミスを防げるため、実務では積極的に活用されています。

Q7. Listのcontainsで重複チェックする方法は?

A7. 新しいリストを用意し、containsメソッドで「まだ追加していない要素か」を確認しながら追加する方法が基本です。ただしcontainsは内部で全要素を走査するため、データ量が多い場合はHashSetを使う方が高速です。

Q8. ソート済みリストで重複チェックを高速化できますか?

A8. 存在確認であればできます。リストが事前にソート済みであれば、Collections.binarySearchを使うことでO(log n)の速度で特定の値が含まれているかを判定できます。ただし重複が複数ある場合にどのインデックスが返されるかは保証されないため、重複の件数を正確にカウントする用途には不向きです。存在確認に限定して使うのが安全です。

まとめ:Java重複チェックは用途で最適解が変わる

重複チェックの実装方法は、目的によって最適解が変わります。

単純な排除ならHashSet、順序保持ならLinkedHashSet、出現回数の集計ならHashMapが基本の選択肢です。

私が開発現場で後輩にアドバイスするときは、まずはSet(HashSet)を使うよう指導しています。

理由は、それがもっともバグが入りにくく、かつパフォーマンスも優れているからです。

迷ったときの判断基準は「まずHashSetで試す → 順序が必要ならLinkedHashSet → 回数が必要ならHashMap → 他の処理と組み合わせるならStream」の順です。

この判断の流れを図にすると、次のようになります。

重複チェックの方法を選ぶ判断フロー(Q1から順に答える)
QUESTION / 質問
はい
いいえ
Q1. 自作クラスを比較する?
equals/hashCodeを実装してQ2へrecordなら自動生成
Q2へ
Q2. 重複の有無だけ分かればよい?
Setに変換してsizeを比較
Q3へ
Q3. 出現回数も知りたい?
HashMap / groupingBy
Q4へ
Q4. 元の順序を保ちたい?
LinkedHashSet
Q5へ
Q5. 並べ替えも必要?
TreeSet
Q6へ
Q6. filterなど他の処理とつなげたい?
Stream.distinct()
HashSet
REVISIONS / 改訂履歴
REV.DATE内容
62026-10-19同じ説明の繰り返しを減らし、読みやすく整えた
52026-10-15図解を描き直し(図面のデザインにそろえた)
42026-08-192026年8月時点の情報で全体を見直し(古い記述を1件更新)
32026-08-142026年8月時点の情報で全体を見直し(古い記述を3件更新)
… ほか1回の改訂
12025-11-23初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

  • この記事を書いた人
  • 最新記事

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門