JavaのSet徹底解説|HashSet・LinkedHashSet・TreeSetの違いと使い方・サンプルコード付き

1. Setとは何か?

Javaプログラミングにおいて、「Set(セット)」は非常に重要なコレクションの一つです。Setは「集合」という意味を持ち、数学における集合と同様に同じ要素を重複して持つことができないという特徴を持っています。たとえば、数値や文字列、オブジェクトなど、どんなデータ型でも「一意な値」のみを集めて管理したい場合に活用されます。

Listとの違いは?

Javaのコレクションフレームワークには、他にもListやMapといったデータ構造があります。中でもListとSetはよく比較されますが、両者の主な違いは以下の通りです。

  • List:同じ値を複数回格納でき、要素の順序(インデックス)も保持される。
  • Set:値の重複が許されず、要素の順序は保証されない(※一部のSet実装を除く)。

つまり、Listは「順番付きのデータの集まり」、Setは「一意なデータの集まり」と言えます。例えば、「重複することなくユーザーIDだけを管理したい」といったケースでは、Setを利用するのが最適です。

Setを使うメリット

  • 重複排除が自動でできる
    たとえば、ユーザーから大量のデータを受け取った場合でも、Setに追加するだけで同じ値は一度しか保存されません。これは重複チェックのコストを意識する必要がなくなり、実装の簡略化にもつながります。
  • 効率的な検索や削除
    Setは要素の存在判定や削除処理が高速に行えるように設計されています(具体的にはHashSetやTreeSetなどで異なります)。

どんな場面で使う?

  • ユーザーのメールアドレスやIDなど、重複しては困る情報を管理したいとき
  • データの一意性を保つ必要がある場合
  • 大量データの中から「重複のないリスト」を効率よく作りたいとき

このように、SetはJavaのプログラムにおいて「重複を許さない集合データ」をスマートに扱うための標準的な仕組みです。以降のセクションでは、Setの基本仕様や使い方、具体的なコード例について詳しく解説していきます。

2. Setの基本仕様と利点

JavaにおけるSetは、java.util.Setインターフェースによって定義されています。このインターフェースを実装することで、「重複しない一意な要素の集まり」を表現できるようになっています。Setの根本的な仕様や使う上での利点について詳しく見ていきましょう。

Setインターフェースの基本仕様

Setは、以下の特徴を持ちます。

  • 重複要素を持てない
    Setに要素を追加しようとしても、既に同じ値が存在していれば追加されません。たとえば、set.add("apple") を2回実行しても、Set内には “apple” が1つだけ保存されます。
  • 格納順序は保証されない(実装による)
    Set自体は要素の順序を保証しません。ただし、後述するLinkedHashSetTreeSetなど、一部の実装では特定の順序で管理されます。
  • null要素の扱い
    Setの実装によっては、nullの要素も格納可能です。例えばHashSetnullを1つだけ格納できますが、TreeSetは許可しません。

equalsとhashCodeの重要性

Setで「重複」と判断されるかどうかは、equalsメソッドhashCodeメソッドによって決まります。特に、自作クラスをSetの要素として利用する場合、この2つのメソッドを適切にオーバーライドしておかないと、意図しない重複や格納ミスが発生します。

  • equals:2つのオブジェクトが等しいかどうかを判定
  • hashCode:オブジェクトを識別するための数値(主に高速化のため)

Setの利点

Setには、以下のような実用上のメリットがあります。

  • 重複排除が簡単
    Setに値をどんどん追加するだけで、同じ値が2回以上登録されることはありません。手動で重複チェックする手間が省けます。
  • 検索・削除が効率的
    実装によりますが、HashSetなどは値の検索や削除が高速に行えます(多くの場合、Listよりも高パフォーマンス)。
  • シンプルなAPI
    addremovecontainsといった基本メソッドだけで直感的に操作できます。

内部実装とパフォーマンス

Setの代表的な実装であるHashSetは、内部的にHashMapを使って要素を管理しています。これにより、要素の追加・削除・検索が平均して高速(O(1))で行えるのが大きな強みです。一方、順序やソートを重視したい場合はLinkedHashSetTreeSetなど、用途に応じた選択が可能です。

3. 代表的な実装クラスとその特徴

JavaのSetには、いくつかの代表的な実装クラスが用意されています。それぞれ特徴が異なるため、用途に合わせて選ぶことが重要です。ここでは、特によく使われる3つのクラス――HashSetLinkedHashSetTreeSet――の特徴と使い分けについて解説します。

HashSet

最も一般的なSetの実装がHashSetです。

  • 特徴
  • 要素の順序を保持しません(格納した順番と取り出す順番が一致しません)。
  • 内部的にはHashMapを利用しており、追加・検索・削除の処理が高速です。
  • null値も1つだけ格納可能です。
  • 主な用途
  • 「順序は問わないが、重複をなくしたい」という場面に最適です。
  • サンプルコード
  Set<String> set = new HashSet<>();
  set.add("apple");
  set.add("banana");
  set.add("apple"); // 重複は無視される

  for (String s : set) {
      System.out.println(s); // "apple" と "banana" のみ表示
  }

LinkedHashSet

LinkedHashSetは、HashSetの機能に挿入順の保持を追加した実装です。

  • 特徴
  • 要素を格納した順番で取り出すことができます。
  • 内部的にはハッシュテーブルと連結リストを組み合わせて管理しています。
  • HashSetよりは若干処理速度が劣りますが、順序を保ちたい場合に有効です。
  • 主な用途
  • 「重複は排除したいが、入力した順番通りにデータを扱いたい」ときに最適です。
  • サンプルコード
  Set<String> set = new LinkedHashSet<>();
  set.add("apple");
  set.add("banana");
  set.add("orange");

  for (String s : set) {
      System.out.println(s); // apple, banana, orange の順で表示
  }

TreeSet

TreeSetは、要素を自動的にソートして管理できるSetです。

  • 特徴
  • 内部的には赤黒木というデータ構造(バランス木)を利用し、要素が自動で「昇順」に並びます。
  • Comparableインターフェース、またはComparatorを使って独自の順序でソートも可能です。
  • null値は格納できません。
  • 主な用途
  • 「重複なし+自動ソート」を同時に実現したい場面で活躍します。
  • サンプルコード
  Set<Integer> set = new TreeSet<>();
  set.add(30);
  set.add(10);
  set.add(20);

  for (Integer n : set) {
      System.out.println(n); // 10, 20, 30 の順で表示
  }

まとめ

  • HashSet:順序は不要、重複排除と高速性重視
  • LinkedHashSet:順序も重視したい場合
  • TreeSet:自動ソートも必要な場合

このように、同じSetでも用途や目的によって最適なクラスは異なります。自分のニーズに合ったSetを選び、賢く使い分けましょう。

4. 基本的なメソッド一覧と使い方

JavaのSetインターフェースには、コレクション操作に必要なさまざまなメソッドが用意されています。ここでは、主なメソッドとその使い方をわかりやすく解説します。実際のコード例も交えて説明しますので、初めてSetを扱う方も安心して学べます。

主なメソッド一覧

  • add(E e)
    指定した要素をSetに追加します。すでに同じ値が存在する場合は追加されません。
  • remove(Object o)
    指定した要素をSetから削除します。削除に成功するとtrueを返します。
  • contains(Object o)
    指定した要素がSetに含まれているかどうかを判定します。
  • size()
    Set内の要素数(サイズ)を取得します。
  • clear()
    Set内のすべての要素を削除し、空にします。
  • isEmpty()
    Setが空かどうかを判定します。
  • iterator()
    Setの要素を順に取り出すためのイテレータ(Iterator)を取得します。
  • toArray()
    Set内の要素を配列に変換します。

基本的な使い方(サンプルコード)

Set<String> set = new HashSet<>();

// 要素の追加
set.add("apple");
set.add("banana");
set.add("apple"); // 重複は無視される

// 要素数の取得
System.out.println(set.size()); // 2

// 要素の存在確認
System.out.println(set.contains("banana")); // true

// 要素の削除
set.remove("banana");
System.out.println(set.contains("banana")); // false

// すべての要素を削除
set.clear();
System.out.println(set.isEmpty()); // true

Iteratorによる要素の走査

Setはインデックスによるアクセス(例:set.get(0))ができないため、要素を順に処理したい場合はIteratorや拡張for文(for-each)を使います。

// 拡張for文を使った要素の走査
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");

for (String s : set) {
    System.out.println(s);
}
// Iteratorを使った走査
Iterator<String> it = set.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
}

注意点

  • Setは重複を許さないため、addメソッドで既存の要素を再度追加しても変化しません。
  • 要素の順序は実装によって異なります(HashSetは順序保証なし、LinkedHashSetは挿入順、TreeSetは自動ソート)。

5. よくある使い方・典型ユースケース

JavaのSetは「重複を許さない」という特徴を生かして、さまざまな場面で活躍します。ここでは、実際の現場やプログラム作成時によく使われる典型的なユースケースを紹介します。

重複のないリストの作成(重複排除)

大量のデータから重複を除いて一意な値だけを抽出したい場合、Setが非常に便利です。たとえば、ユーザーが入力した複数の値から、重複する要素を自動的に取り除きたいときなどに使われます。

例:Listから重複を除去したSetを作成する

List<String> list = Arrays.asList("apple", "banana", "apple", "orange");
Set<String> set = new HashSet<>(list);

System.out.println(set); // [apple, banana, orange]

 

入力データの一意性チェック

たとえば、「ユーザーID」や「メールアドレス」など、同じ値が複数登録されてはいけないケースにもSetが役立ちます。登録前にSetに追加できるかどうかで、すでに登録されているかを即座に判定できます。

Set<String> emailSet = new HashSet<>();
boolean added = emailSet.add("user@example.com");
if (!added) {
    System.out.println("すでに登録されています");
}

自作クラスの格納とequals/hashCodeの実装

自分で作成したクラスをSetに格納する場合、equalsとhashCodeの実装が必要です。これを正しく実装しないと、同じ内容のインスタンスが重複して追加されてしまうことがあります。

例:Personクラスの一意性の担保

class Person {
    String name;
    Person(String name) { this.name = name; }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

// Setへの追加例
Set<Person> people = new HashSet<>();
people.add(new Person("Taro"));
people.add(new Person("Taro")); // equals/hashCode未実装だと重複することがある
System.out.println(people.size()); // 1

データ変換や高速な存在判定

Setはcontainsメソッドで要素の存在を高速に判定できるため、フィルタリングや照合処理にもよく使われます。
たとえば、あるリストの中で「特定のキーワードが含まれているか」などを繰り返し調べる場合、Setに変換しておくと大幅に処理効率が向上します。

例:高速な存在判定

Set<String> keywordSet = new HashSet<>(Arrays.asList("java", "python", "c"));
boolean found = keywordSet.contains("python"); // true

このように、Setは日常的なプログラミングやシステム開発の中で「一意性保証」「重複排除」「高速な検索」など、さまざまなシーンで活用されています。

6. パフォーマンスと注意点

JavaのSetは、効率的に重複のない要素を管理できる便利なコレクションですが、使い方によっては思わぬ落とし穴やパフォーマンスの違いが生じることがあります。ここでは、Setを使用する際に知っておきたいパフォーマンスの特徴と注意点を解説します。

実装ごとのパフォーマンスの違い

  • HashSet
    内部的にハッシュテーブルを利用しているため、要素の追加・削除・検索が平均して高速(O(1))です。ただし、要素数が極端に増えたり、ハッシュ値の衝突が多発するとパフォーマンスが低下することもあります。
  • LinkedHashSet
    基本的にはHashSetと同じく高速ですが、要素の順序情報も保持するため、ややオーバーヘッドがあります。とはいえ、数万件規模でなければ体感できるほどの差はありません。
  • TreeSet
    内部的に赤黒木という木構造を利用するため、要素の追加・削除・検索はいずれもO(log n) です。HashSetよりは遅いですが、常に自動でソートされるメリットがあります。

可変オブジェクト(mutable object)を要素にする際の注意点

Setに格納する要素が可変(値が途中で変わる)オブジェクトの場合は特に注意が必要です。
たとえば、HashSetやTreeSetは、hashCodecompareToの値をもとに要素を管理します。そのため、Setに追加した後でオブジェクトの中身(比較の基準になる値)を変更してしまうと、検索や削除が正常に機能しなくなります

例:可変オブジェクトの落とし穴

Set<Person> people = new HashSet<>();
Person p = new Person("Taro");
people.add(p);

p.name = "Jiro"; // Set追加後にプロパティを変更
people.contains(p); // 正常に見つからない可能性がある

このようなトラブルを防ぐためには、Setの要素にはできるだけ不変(immutable)オブジェクトを使うのがおすすめです。

null値の扱い

  • HashSet・LinkedHashSet
    1つだけnull値を格納できます。
  • TreeSet
    null値は原則として格納できません(NullPointerExceptionが発生)。

その他の注意点

  • イテレーション中の変更
    Setをイテレーション(繰り返し処理)している最中に、同じSetに要素の追加や削除を行うと、ConcurrentModificationExceptionという例外が発生する場合があります。要素を変更する場合は、Iteratorremoveメソッドを使いましょう。
  • 順序を意識したい場合の選択
    データの追加順やソート順を重視するなら、LinkedHashSetTreeSetを選びましょう。HashSetは順序が保証されません。

Setは便利な反面、使い方を誤ると思わぬバグやパフォーマンス低下につながることもあります。用途や状況に合わせて適切な実装を選び、注意点を押さえたコーディングを心がけましょう。

7. 比較チャート(一覧形式)

ここでは、Javaの代表的なSet実装クラスである「HashSet」「LinkedHashSet」「TreeSet」の違いを一目で分かるように表形式でまとめます。それぞれの特徴や用途を整理することで、目的に合ったSetの選択がしやすくなります。

実装クラス重複禁止順序保持ソートパフォーマンスnull値主な用途
HashSet×(順不同)×高速(O(1))1つだけ可重複排除、順序不要
LinkedHashSet〇(挿入順)×HashSetよりやや遅い1つだけ可重複排除、挿入順を保ちたい場合
TreeSet×〇(自動ソート)高速(O(log n))不可重複排除、自動ソートが必要な場合

ポイントまとめ

  • HashSet
    最も一般的なSet実装。順序を気にしないなら、迷わずこれ。要素の存在判定や重複排除が高速です。
  • LinkedHashSet
    要素を追加した順番を保持したい場合に最適。履歴管理や順序付きデータの保持に活躍します。
  • TreeSet
    自動的に要素をソートしたい場合はこちら。自然順(Comparable)や任意の順序(Comparator)で並べ替えが可能です。

この比較表を参考に、自分のアプリケーションや要件にぴったりのSetを選びましょう。

8. よくある質問(FAQ)

JavaのSetを利用する際に、初心者から現場の開発者までがよく疑問に思うポイントについて、Q&A形式でまとめました。

Q1. Setの要素にプリミティブ型(intやcharなど)は使えますか?
A1. 直接プリミティブ型は使えません。代わりに、IntegerCharacterなどのラッパークラスを使ってください。
例:Set<Integer> set = new HashSet<>();

Q2. Setに同じ値を複数回追加した場合はどうなりますか?
A2. 2回目以降の追加は無視され、Setには一度しか保存されません。また、addメソッドは追加できた場合のみtrueを返し、既に存在していた場合はfalseを返します。

Q3. ListとSetの使い分けは?
A3. 要素の順序や重複を許したい場合はList一意な要素だけを管理したい場合はSetを使います。
Listは順序も保持されますが、Setは順序を保証しない(もしくは実装依存)点に注意しましょう。

Q4. 自作クラスをSetに格納するには何が必要?
A4. equalshashCodeメソッドの適切なオーバーライドが必須です。これらを正しく実装しないと、期待通りに重複排除できません。

Q5. Setの要素の順序を保持したい場合は?
A5. LinkedHashSetを使うと、追加した順序をそのまま維持できます。

Q6. 要素をソートして管理したい場合は?
A6. TreeSetを使うと、自然順または任意のComparatorによる順序で自動的にソートされます。

Q7. Setの要素にnullを格納できますか?
A7. HashSetLinkedHashSetはnullを1つだけ格納できますが、TreeSetはnullを格納できません(NullPointerExceptionが発生します)。

Q8. Setのサイズを調べるには?
A8. size()メソッドで、現在Setに含まれている要素数を取得できます。

Q9. Setを配列やListに変換するには?
A9.

  • 配列に変換:toArray()メソッドを利用
  • Listに変換:new ArrayList<>(set) のようにListコンストラクタを使う

Q10. イテレーション中に要素を削除できますか?
A10. 可能ですが、その場合はIteratorremove()メソッドを使用してください。拡張for文(for-each)で直接Setから削除すると、例外が発生することがあります。

9. まとめ

本記事では、JavaのSetについて基礎から応用まで幅広く解説してきました。最後に、要点を整理しておきます。

  • Setは「重複しない一意な要素の集合」を管理するためのコレクションです。Listのような順序や重複は扱わず、重複を自動的に排除したい場合に最適です。
  • 代表的な実装クラスとして、HashSet(順序なし・高速)LinkedHashSet(挿入順保持)TreeSet(自動ソート)があり、用途に応じて選択することが大切です。
  • Setの利用でよくあるのは「重複の排除」「入力データの一意性チェック」「自作クラスの集合管理」「高速な存在判定」など、日常的なシーンで役立つ機能が揃っています。
  • パフォーマンスや注意点としては、可変オブジェクトの利用、イテレーション中の変更、null値の扱いなどがあり、基本仕様を理解して使うことが重要です。
  • 実装ごとの特徴を比較表でまとめましたので、要件に応じて最適なSetを選んでください。
  • FAQでよくある疑問も網羅しましたので、初学者から現場のエンジニアまで安心して使えるでしょう。

Setを使いこなすことで、Javaのプログラミングがさらにシンプルで効率的になります。今後はListやMapなど他のコレクションとの組み合わせや、より高度な使い方にもぜひチャレンジしてみてください。