【Java】JavaのSet入門:HashSetとTreeSetの違いを初心者向けに解説

Java

こんにちは、かつコーチです。
前回はListを解説しましたが、今回は「重複を許さないデータの集まり」を扱うSetについて説明します。
「Listと何が違うの?」「HashSetとTreeSetはどう使い分ける?」という疑問を、実際のコードとつまずき事例を交えて解消していきます。

Setとは?

重複を許さないコレクション

Set(値の重複を許さない集合を表すインターフェース)は、同じ値を2回追加しても1つしか保持されないという特徴があります。
Listとの一番の違いはここです。

List<String> list = new ArrayList<>();
list.add("A");
list.add("A");
System.out.println(list); // [A, A] 重複OK

Set<String> set = new HashSet<>();
set.add("A");
set.add("A");
System.out.println(set); // [A] 重複は無視される

なぜ重複を排除する仕組みが必要なのか

「メールアドレスの重複登録を防ぎたい」「タグの一覧から同じタグを1つだけ表示したい」など、業務ロジックの中で「同じ値は1つだけ欲しい」場面は頻繁にあります。
毎回自分で重複チェックのコードを書くよりも、Setを使えば自然に重複が排除されます。

基本の書き方

HashSetの基本操作

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

public class HashSetSample {
    public static void main(String[] args) {
        Set<String> tags = new HashSet<>();
        tags.add("Java");
        tags.add("初心者向け");
        tags.add("Java"); // 重複は無視される

        System.out.println(tags.size()); // 2
        System.out.println(tags.contains("Java")); // true
    }
}

HashSetは内部でハッシュ値(オブジェクトの内容を元に計算される数値)を使って要素を管理する実装です。
追加・検索・削除がいずれも高速な反面、要素を取り出す順番は保証されません。

TreeSetの基本操作

import java.util.Set;
import java.util.TreeSet;

public class TreeSetSample {
    public static void main(String[] args) {
        Set<Integer> numbers = new TreeSet<>();
        numbers.add(5);
        numbers.add(1);
        numbers.add(3);

        System.out.println(numbers); // [1, 3, 5] 自動で昇順に並ぶ
    }
}

TreeSetは要素を常に一定の順序(デフォルトは昇順)で保持する実装です。
内部では赤黒木という木構造でデータを管理しているため、順序を保ちながら検索・追加ができます。

順序が必要かどうかで実装を選ぶ

特徴HashSetTreeSet
順序保証されない常にソートされた順序
速度速い(O(1)相当)やや遅い(O(log n))
null要素1つだけ許可許可しない(例外発生)
主な用途重複排除だけしたい重複排除しつつ順序も欲しい

よくあるつまずきポイント・エラー対処

自作クラスをSetに入れたら重複判定されなかった

実際に私がハマった経験ですが、自作クラスをHashSetに入れたときに、同じ内容のはずのデータが重複扱いされてしまったことがあります。

// ❌Before:equalsとhashCodeを実装していない自作クラス
class User {
    String name;
    User(String name) { this.name = name; }
}

Set<User> users = new HashSet<>();
users.add(new User("かつコーチ"));
users.add(new User("かつコーチ"));
System.out.println(users.size()); // 2(同じ名前なのに別物扱いされる)

HashSetは重複判定にequalshashCodeメソッドを使いますが、自作クラスはデフォルトで「同じインスタンスかどうか」でしか比較しません。
そのため見た目が同じデータでも別のオブジェクトとして扱われてしまいます。

// ✅After:equalsとhashCodeをオーバーライドする
class User {
    String name;
    User(String name) { this.name = name; }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof User other)) return false;
        return this.name.equals(other.name);
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

Set<User> users = new HashSet<>();
users.add(new User("かつコーチ"));
users.add(new User("かつコーチ"));
System.out.println(users.size()); // 1(正しく重複排除される)

自作クラスをHashSetやHashMapのキーに使うときは、equalshashCodeを必ずセットでオーバーライドすることを覚えておいてください。

応用・一歩先の使い方

追加した順序を保ちたいならLinkedHashSet

HashSetは順序が保証されませんが、「追加した順番のまま保持したい」場合はLinkedHashSetが使えます。

Set<String> ordered = new java.util.LinkedHashSet<>();
ordered.add("三番目に見せたい項目");
ordered.add("一番目に見せたい項目");
System.out.println(ordered); // 追加順のまま表示される

「重複は排除したいが、表示順は制御したい」という場面ではLinkedHashSetが便利です。

Listから重複を除去する定番パターン

List<String> withDuplicates = List.of("A", "B", "A", "C", "B");
Set<String> unique = new HashSet<>(withDuplicates);
System.out.println(unique.size()); // 3

ListをそのままHashSetのコンストラクタに渡すだけで、簡単に重複を取り除けます。

まとめ

この記事のポイント

  • Setは値の重複を許さない集合を表すインターフェース
  • HashSetは高速だが順序は保証されない
  • TreeSetは常にソートされた順序を保つが、やや速度は落ちる
  • 自作クラスをSetに入れるときはequalshashCodeのオーバーライドが必須
  • 追加順を保ちたいときはLinkedHashSetを使う

次に読むべき記事

  • Listの基本:ArrayListとLinkedListの違い
  • Mapの基本:HashMap・TreeMap・LinkedHashMapの違い

タグ: Java, 初心者向け, コレクション

タイトルとURLをコピーしました