導入
Javaでプログラミングをしていると、「要素を重複させたくない」かつ「常に昇順で並べておきたい」という場面に遭遇することは多々あります。ListやHashSetでは実現できないこの要件を、効率的に解決してくれるのが java.util.TreeSet です。今回は、なぜTreeSetが現場で重宝されるのか、その内部構造と使い方のコツを解説します。
基礎知識
TreeSet は、Setインターフェースを実装したクラスで、内部的に 赤黒木(Red-Black Tree) と呼ばれる自己平衡二分探索木を使用しています。
このデータ構造のおかげで、要素の追加・削除・検索といった操作が、常に安定した対数時間(O(log n))で行われます。何よりも重要な特徴は、要素が常に自然順序付け(Comparable)またはコンパレータ(Comparator)に従ってソートされた状態で保持される点です。
実装/解決策
TreeSetを利用する際は、格納するオブジェクトが Comparableインターフェース を実装しているか、コンストラクタで Comparator を渡す必要があります。これらがない場合、実行時に ClassCastException が発生するため注意が必要です。また、HashSetと異なり、nullを許可しない(あるいは比較できない)点も設計上の重要なポイントです。
サンプルプログラム
以下のコードは、文字列をTreeSetに格納し、自動的にソートされる様子を確認する例です。
import java.util.TreeSet;
import java.util.Iterator;
public class TreeSetExample {
public static void main(String[] args) {
// TreeSetのインスタンス化(自然順序でソートされます)
TreeSet<String> fruits = new TreeSet<>();
// 順不同で追加
fruits.add("Orange");
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Grape");
// 既に重複排除され、アルファベット順にソートされている
System.out.println("ソート済みセットの内容:");
for (String fruit : fruits) {
System.out.println(fruit); // Apple, Banana, Grape, Orangeの順で出力
}
// 応用: 範囲指定の取得(subSetメソッド)
System.out.println("BananaからOrange未満を取得:");
System.out.println(fruits.subSet("Banana", "Orange"));
}
}
応用・注意点
現場でTreeSetを使う際に最も注意すべきは、「equalsとcompareToの整合性」です。
TreeSetは、要素の重複判定に「equals」ではなく「compareTo(またはcompare)の結果が0であるか」を使用します。もし、equalsでは等しいと判定されるのに、compareToでは0を返さないようなクラスを設計してしまうと、Setの定義(要素の重複を許さない)が崩れてしまい、予期せぬバグの温床となります。
また、Java 21から導入された Sequenced Collections により、TreeSetでも最初の要素(first())や最後の要素(last())へのアクセスがより直感的に行えるようになりました。パフォーマンスが必要な場面では、単純なソート処理ではなく、TreeSetの持つ「検索・範囲取得」の特性を活かした設計を心がけましょう。

コメント