0Pricing
Java Academy · レッスン

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, apple

TreeSet 構築時の 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()); // 1

null セーフな 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.99

Comparator の取得

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());      // null

Comparator の合成

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. TreeMap:ソートされたキーと値のペア
  2. サブマップと範囲ビュー
  3. TreeSet と NavigableSet
  4. Tree コレクションのカスタム順序
← Java Academyに戻る