1. 導入:なぜSetの使い分けが重要か
Java開発において「重複を許さない集合」を扱う際、何気なくHashSetを選択していませんか?実務では、要素の順序、検索速度、メモリ効率の観点から、適切な実装クラスを選ぶことがパフォーマンスと可読性に直結します。本記事では、Set系コレクションの特性を整理し、現場で迷わないための判断基準を解説します。
2. 基礎知識:Setインターフェースの主要な実装クラス
Setは「要素の重複を許さない」という特性を持つインターフェースです。主に以下の3つの実装が頻繁に使われます。
・HashSet: ハッシュテーブルを使用。順序は一切保証されません。検索、追加、削除が定数時間(O(1))で行えるため、最も高速です。
・TreeSet: 赤黒木(平衡二分探索木)を使用。要素は「自然順序」または「Comparator」に従ってソートされます。検索や追加は対数時間(O(log n))です。
・LinkedHashSet: ハッシュテーブルと連結リストを組み合わせたもの。要素が挿入された順序を保持します。HashSetに近い速度を維持しつつ、順序を維持できるのが強みです。
3. 実装・解決策:状況に応じた選択指針
実務での選定基準は極めてシンプルです。
1. 順序が不要で、とにかく速度を優先したい場合 -> HashSet
2. データをソートした状態で保持したい場合 -> TreeSet
3. 要素を追加した順序を保持しつつ、重複排除を行いたい場合 -> LinkedHashSet
注意点として、Java 21から導入されたSequenced Collectionsにより、LinkedHashSetは「先頭や末尾へのアクセス」が標準化されました。これにより、順序を意識した操作がこれまで以上に容易になっています。
4. サンプルプログラム:特性の違いを確認する
以下のコードをコピーして、各Setの挙動の違いを比較してみてください。
import java.util.;
public class SetComparison {
public static void main(String[] args) {
// HashSet: 順序はバラバラ
Set
System.out.println(“HashSet: ” + hashSet);
// TreeSet: アルファベット順にソートされる
Set
System.out.println(“TreeSet: ” + treeSet);
// LinkedHashSet: 追加した順序を保持
Set
linkedHashSet.add(“Banana”);
linkedHashSet.add(“Apple”);
linkedHashSet.add(“Orange”);
System.out.println(“LinkedHashSet: ” + linkedHashSet);
// Java 21以降のSequenced Collections(LinkedHashSetで利用可能)
if (linkedHashSet instanceof SequencedSet
System.out.println(“先頭要素: ” + sSet.getFirst());
}
}
}
5. 応用・注意点:現場で陥りやすい罠
・hashCode/equalsの契約: HashSetやLinkedHashSetに自作クラスを格納する場合、必ずequals()とhashCode()を適切にオーバーライドしてください。これを怠ると、重複排除が正しく機能せず、メモリリークやバグの原因になります。
・TreeSetとnull: TreeSetは要素の比較を行うため、nullを格納しようとするとNullPointerExceptionが発生することがあります(Comparatorの設定による)。HashSet系はnullを許容しますが、TreeSetは基本的に許容しません。
・スレッドセーフティ: これらのクラスはスレッドセーフではありません。マルチスレッド環境で共有する場合は、Collections.synchronizedSet()を使用するか、java.util.concurrent配下のConcurrentHashMapのキーセットを利用することを検討してください。
適切なコレクション選びは、コードの品質を高める第一歩です。要件に合わせて最適なクラスを選択し、効率的な実装を心がけましょう。

コメント