Java | Java 標準ライブラリ:Set / HashSet

Java Java
スポンサーリンク

Set / HashSet をざっくりイメージする

まず感覚からいきます。

List は「順番がある・重複を許す」コレクションでした。
それに対して Set は、

  • 「順番は気にしない」
  • 「同じ要素を二度入れない(重複禁止)」

という性質を持つコレクションです。

Set はインターフェース(抽象的な型)、
HashSet はその代表的な実装クラスです。

コードではだいたいこう書きます。

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

「同じものが入っているかどうかだけ管理したい」
「集合として扱いたい(重複いらない)」
といった場面で、Set / HashSet は非常に頼りになる存在です。


List と Set の違いを最初にハッキリさせる

「重複 OK で順序が大事」なら List

List はこんな性質でした。

  • 要素に順番がある(インデックスでアクセスできる)
  • 同じ値を何度でも入れてよい
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("apple"); // 重複してもOK

System.out.println(list); // [apple, banana, apple]
Java

「3番目の要素は?」など、位置が重要なときは List を使います。

「重複なしで、順番どうでもいい」なら Set

Set(特に HashSet)は逆の性質です。

  • 同じ値を二度入れても 1 回しか保持しない(重複禁止)
  • 要素の順番は保証されない(インデックスもない)
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple");   // 同じ値を追加

System.out.println(set); // [banana, apple] など(順番はバラバラだが "apple" は1つだけ)
Java

「この値が“存在するかどうか”だけ知りたい」
「とにかくユニークな集合がほしい」
といった場面では、Set が List よりずっと自然です。


HashSet の基本操作(add / remove / contains / size)

要素を追加する add(すでにあれば増えない)

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

set.add("Alice");
set.add("Bob");
set.add("Alice");     // 同じ要素を追加

System.out.println(set);      // [Alice, Bob] (Alice は一つだけ)
System.out.println(set.size()); // 2
Java

add は「集合にその要素を含める」イメージです。

すでに同じ値が入っている場合、
内部では「何も変わらない」ことが多いです。

add の戻り値は boolean で、

  • 新しい要素が追加された → true
  • すでに入っていて変化なし → false

という意味になります。

boolean added1 = set.add("Carol"); // true(新規追加)
boolean added2 = set.add("Alice"); // false(もともとあった)
Java

要素を削除する remove

set.remove("Bob");       // Bob を集合から取り除く
System.out.println(set); // [Alice, Carol] など
Java

存在しない要素を remove してもエラーにはなりません。
その場合、戻り値が false になります。

「含まれているか?」を調べる contains(これが Set の真骨頂)

Set の一番の用途は「こいつ、入ってる?」の高速判定です。

if (set.contains("Alice")) {
    System.out.println("Alice はすでに登録済み");
}
Java

List で同じことをやると、先頭から順に探していくことになり、
要素数が増えるほど遅くなります。

HashSet は内部でハッシュテーブルを使っているので、
多くの場合、要素数が増えても「含まれているかどうか」の判定が非常に速いです。


HashSet の「順番が保証されない」という大事な性質

表示順に意味を期待してはいけない

HashSet は要素の順番を保証しません。

Set<Integer> set = new HashSet<>();
set.add(10);
set.add(20);
set.add(30);
set.add(40);

System.out.println(set); // [20, 40, 10, 30] など(実行ごとに変わりうる)
Java

よくある誤解は、

  • 「追加した順に出てくるはず」と思ってしまう
  • 「表示された順番に意味がある」と考えてしまう

ことです。

順序に意味がある処理(ランキング、表示順、並び替え)に Set を使うのは向いていません。
順番に意味があるなら List、順番はどうでもよく重複禁止なら Set と考えてください。

もし「重複禁止+追加順を維持したい」ときは、
LinkedHashSet という別の実装がありますが、それは一段先の話として覚えておけば十分です。


重複を取り除きたいときに Set が超便利

List から重複を消してユニークな集合を作る

例えば、ユーザーから入力された文字列のリストがあったとします。

List<String> list = Arrays.asList("apple", "banana", "apple", "orange", "banana");
Java

重複を取り除いて「一意な値だけ」にしたいなら、
いったん HashSet に突っ込むのが手っ取り早いです。

Set<String> set = new HashSet<>(list);
System.out.println(set);  // [banana, orange, apple] など(順番はバラバラ)
Java

順番は問わない(とにかくユニークな集合が欲しい)なら、これで終わりです。

もし「重複を消しつつ、元の順番で List に戻したい」という場合は、

  1. Set ではなく LinkedHashSet を使う
  2. そのあと new ArrayList<>(set) で List に戻す

というパターンがよく使われます。

会員 ID のチェック/既存登録チェックなど

例えば、会員 ID がユニークかどうかチェックしたい場合。

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

public boolean register(String id) {
    if (ids.contains(id)) {
        return false;           // すでに存在している
    }
    ids.add(id);
    return true;
}
Java

これを List でやると、contains が遅くなりやすいです。
Set にしておけば、大量のIDを扱う場合でも快適になります。


equals / hashCode と HashSet の関係(ここは少し深掘り)

HashSet は「hashCode と equals」で重複判定をしている

HashSet が「同じ要素」とみなす条件は、基本的に次の2つです。

  • hashCode() の結果が同じ
  • equals()true を返す

String, Integer など標準クラスは、この2つがうまく実装されています。

Set<String> set = new HashSet<>();
set.add("abc");
set.add(new String("abc"));

System.out.println(set.size()); // 1
Java

"abc"new String("abc") は別インスタンスですが、
equals で「中身の文字列が同じ」なので、Set では同じ要素とみなされます。

自作クラスを Set に入れるときの注意

自分で定義したクラスを HashSet に入れる場合、
equalshashCode をちゃんと実装しないと、
「見た目は同じなのに別物として扱われる」ということが起こります。

class User {
    String name;
    User(String name) { this.name = name; }
}

Set<User> set = new HashSet<>();
set.add(new User("Alice"));
set.add(new User("Alice"));

System.out.println(set.size()); // 2(equals/hashCode を未実装だと別物扱い)
Java

これを「名前が同じなら同一ユーザーとみなしたい」という仕様にしたいなら、
equalshashCode を上書きしてあげます。

class User {
    String name;
    User(String name) { this.name = name; }

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

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

これで、

Set<User> set = new HashSet<>();
set.add(new User("Alice"));
set.add(new User("Alice"));

System.out.println(set.size()); // 1(重複とみなされる)
Java

となります。

初心者の段階では、
「Set に自作クラスを入れるなら equals/hashCode のことを思い出す」
くらい意識しておけば十分です。


Set を for / 拡張 for で回す(順番に依存しない書き方)

for-each で全要素をなめる

Set はインデックスを持たないので、get(0) のようなアクセスはできません。
基本的なループは「拡張 for(for-each)」が定番です。

Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");

for (String s : set) {
    System.out.println(s);  // A, B, C(順番は保証されない)
}
Java

このとき、要素が出てくる順序には意味を持たせないようにしてください。
Set を使っている以上、「順番はどうでもいい設計」になっているべきです。

もし「N 番目」という概念が必要になってきたら、その時点で List を検討するサインです。


まとめ:Set / HashSet を頭の中でこう位置づける

初心者向けに Set / HashSet の本質をまとめると、こうなります。

  • Set は「重複を許さないコレクション」。List のようにインデックスは持たない。
  • HashSet は Set の代表的な実装で、「順番は保証しないが、存在チェックが速い」。
  • add で要素を入れると、同じ値は1回しか保持されない。
  • contains で「この値が集合に含まれているか」を高速に判定できる。
  • 順番に意味があるなら List、順番どうでもよく重複禁止なら Set。
  • 自作クラスを入れるときは equals / hashCode を意識する。

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