Tree コレクションのカスタム順序
TreeMap または TreeSet に Comparator を渡し、ドメイン固有のソート順を定義します。
「Tree コレクションのカスタム順序」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
カスタム順序が必要な理由
ツリーコレクション(TreeMap、TreeSet)は、デフォルトで自然順序を使用します。ドメインオブジェクトに自然な順序がない場合や、別のソート順が必要な場合は、構築時に Comparator を指定します。
TreeMap 構築時の Comparator
カスタムルールでキーをソートするには、コンパレータを渡します。たとえば、アルファベットの逆順にできます:
import java.util.*;
TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
map.put("banana", 2);
map.put("apple", 1);
map.put("cherry", 3);
map.forEach((k,v) -> System.out.println(k)); // cherry, banana, appleTreeSet 構築時の Comparator
TreeSet にコンパレータを指定すると、要素の自然順序とは独立してソート順を制御できます:
TreeSet<String> byLength = new TreeSet<>(
Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder())
);
byLength.addAll(List.of("fig","apple","kiwi","date","banana"));
for (String s : byLength) System.out.print(s + " ");
// fig date kiwi apple banana複数のフィールドによるオブジェクトのソート
コンパレータを連結すると、主フィールドでソートした後、同値の場合に副フィールドでソートできます:
record Employee(String name, String dept, int salary) {}
TreeSet<Employee> sorted = new TreeSet<>(
Comparator.comparing(Employee::dept)
.thenComparingInt(Employee::salary).reversed()
.thenComparing(Employee::name)
);
sorted.add(new Employee("Alice", "Eng", 90_000));
sorted.add(new Employee("Bob", "Eng", 85_000));
sorted.add(new Employee("Carol", "HR", 70_000));
for (Employee e : sorted) System.out.println(e.dept()+" "+e.name());equals との整合性
重要なルール: TreeSet/TreeMap が使用するコンパレータは、そのコレクションにおける等価性を定義します。comparator.compare(a, b) == 0 の場合、a.equals(b) が false であっても、a と b は同じキーとみなされます。
// Comparator ignoring case — "apple" and "APPLE" become the same key!
TreeSet<String> ci = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
ci.add("apple");
ci.add("APPLE"); // not added — same by comparator
System.out.println(ci.size()); // 1大文字と小文字を区別しない TreeMap
実用上よくある要件として、大文字と小文字を区別しない文字列キーの TreeMap があります(HTTP ヘッダーや設定キーに便利です):
TreeMap<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
headers.put("Content-Type", "application/json");
headers.put("content-type", "text/html"); // overwrites!
System.out.println(headers.get("CONTENT-TYPE")); // text/html
System.out.println(headers.size()); // 1null セーフな Comparator
自然順序を使用するツリーコレクションでは、null キーに対して NPE がスローされます。null を最小のキーとして許可するには、null セーフなコンパレータを使用します:
TreeMap<String, Integer> map = new TreeMap<>(
Comparator.nullsFirst(Comparator.naturalOrder())
);
map.put(null, 0);
map.put("b", 2);
map.put("a", 1);
map.forEach((k,v) -> System.out.println(k + "=" + v));
// null=0, a=1, b=2ドメインオブジェクトの例: 価格による Product
TreeSet で商品を価格順にソートし、重複を維持しないためのタイブレーカーとして名前を使用します:
record Product(String name, double price) {}
TreeSet<Product> catalog = new TreeSet<>(
Comparator.comparingDouble(Product::price)
.thenComparing(Product::name)
);
catalog.add(new Product("Widget", 9.99));
catalog.add(new Product("Gadget", 24.99));
catalog.add(new Product("Donut", 9.99));
catalog.forEach(p -> System.out.println(p.name() + " $" + p.price()));
// Donut $9.99, Widget $9.99, Gadget $24.99Comparator の取得
TreeMap/TreeSet で comparator() を呼び出すと、カスタムコンパレータを取得できます。自然順序が使用されている場合は null を返します。
TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
System.out.println(map.comparator() != null); // true
TreeMap<String, Integer> natural = new TreeMap<>();
System.out.println(natural.comparator()); // nullComparator の合成
Java の Comparator には、わかりやすく合成するための連結可能なファクトリメソッドが用意されています:
Comparator<String> comp =
Comparator.comparingInt(String::length) // by length
.thenComparing(Comparator.naturalOrder()); // then alphabetically
TreeSet<String> ts = new TreeSet<>(comp);
ts.addAll(List.of("go", "java", "c", "rust", "py"));
ts.forEach(s -> System.out.print(s + " "));
// c go py java rustカスタム Comparator を使用すべきでない場合
TreeMap のキーや TreeSet の要素には、equals と整合しないコンパレータを使用しないでください。そうすると、エントリが「見つからない」ように見える混乱を招きます。すべてのコレクションで正しく動作させるには、compare(a, b) == 0 と a.equals(b) が同値になるようにしてください。
確認問題
ある TreeSet が String::length のみに基づくコンパレータを使用しています。"cat" と "dog" の両方を追加するとどうなりますか?
復習: カスタム順序付け
重要なポイント:
- カスタム順序を定義するには、ComparatorをTreeMap/TreeSetのコンストラクターに渡します
- ツリーコレクションでの一意性は、Comparatorが定義する等価性によって決まります
- 予期しない動作を避けるため、Comparatorとequalsの整合性を確保します
- nullを安全に扱う順序付けには、Comparator.nullsFirst/nullsLastを使用します
- 複数フィールドでのソートには、thenComparingで比較を連結します
よくある質問
「Tree コレクションのカスタム順序」レッスンは無料ですか?
はい。「Tree コレクションのカスタム順序」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「Tree コレクションのカスタム順序」で何を学びますか?
TreeMap または TreeSet に Comparator を渡し、ドメイン固有のソート順を定義します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「Tree コレクションのカスタム順序」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- TreeMap:ソートされたキーと値のペア
- サブマップと範囲ビュー
- TreeSet と NavigableSet
- Tree コレクションのカスタム順序