こんにちは、かつコーチです。
前回は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は要素を常に一定の順序(デフォルトは昇順)で保持する実装です。
内部では赤黒木という木構造でデータを管理しているため、順序を保ちながら検索・追加ができます。
順序が必要かどうかで実装を選ぶ
| 特徴 | HashSet | TreeSet |
|---|---|---|
| 順序 | 保証されない | 常にソートされた順序 |
| 速度 | 速い(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は重複判定にequalsとhashCodeメソッドを使いますが、自作クラスはデフォルトで「同じインスタンスかどうか」でしか比較しません。
そのため見た目が同じデータでも別のオブジェクトとして扱われてしまいます。
// ✅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のキーに使うときは、equalsとhashCodeを必ずセットでオーバーライドすることを覚えておいてください。
応用・一歩先の使い方
追加した順序を保ちたいなら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に入れるときは
equalsとhashCodeのオーバーライドが必須 - 追加順を保ちたいときはLinkedHashSetを使う
次に読むべき記事
- Listの基本:ArrayListとLinkedListの違い
- Mapの基本:HashMap・TreeMap・LinkedHashMapの違い
タグ: Java, 初心者向け, コレクション