Java | Java 標準ライブラリ:TreeMap

Java Java
スポンサーリンク

TreeMap をざっくりイメージする

まず一言でいうと、TreeMap

「キーで常にソートされた順番を保ってくれる Map」

です。

HashMap は「キー → 値」を高速に扱えるけれど、キーの順番はバラバラでした。
LinkedHashMap は「挿入した順」を覚えてくれました。

TreeMap はそれとは違い、

  • キーで「小さい順」「辞書順」に常に整列されている
  • キーの範囲(〇以上△未満、など)で取り出すのが得意

という、順位や範囲を意識した Map です。

コードではだいたいこう書きます。

import java.util.Map;
import java.util.TreeMap;

Map<Integer, String> map = new TreeMap<>();
Java

左を Map(インターフェース)、右を TreeMap にするのは、他の Map と同じ“お作法”です。


HashMap / LinkedHashMap / TreeMap の違いを感覚でつかむ

HashMap は「順番どうでもいいけど速い Map」

Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
map.put("orange", 2);

System.out.println(map);  // {orange=2, apple=3, banana=5} など順不同
Java

キーの順番に意味はなく、「キーで高速に引ければそれでいい」場面向きです。

LinkedHashMap は「挿入順を覚えてくれる Map」

Map<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 3);
map.put("banana", 5);
map.put("orange", 2);

System.out.println(map);  // {apple=3, banana=5, orange=2}
Java

入れた順番どおりに出てくるので、人間向けの表示やログに向いています。

TreeMap は「キーでソートされた順の Map」

Map<String, Integer> map = new TreeMap<>();
map.put("banana", 5);
map.put("apple", 3);
map.put("orange", 2);

System.out.println(map);  // {apple=3, banana=5, orange=2}(キーの辞書順)
Java

入れた順ではなく、「キーの順序」に従って並びます。
Integer なら小さい順、String なら辞書順です。

「キーの順番に意味がある」「キーの範囲を使った処理がしたい」
というときに、TreeMap が真価を発揮します。


TreeMap の基本操作(put / get / remove)はほぼ同じ

まずは普通の Map として使ってみる

import java.util.Map;
import java.util.TreeMap;

public class TreeMapBasic {
    public static void main(String[] args) {
        Map<Integer, String> gradeMap = new TreeMap<>();

        gradeMap.put(3, "中学3年");
        gradeMap.put(1, "中学1年");
        gradeMap.put(2, "中学2年");

        System.out.println(gradeMap.get(2)); // 中学2年

        gradeMap.remove(1);

        System.out.println(gradeMap);        // {2=中学2年, 3=中学3年}
    }
}
Java

put, get, remove, containsKey など、
基本メソッドの使い方は HashMap とほぼ同じです。

違うのは「格納されている内部順序」だけです。
System.out.println(gradeMap) したときの表示順が「キーの昇順」になっているのがポイントです。

ループしたときに必ずキー順になる

for (Map.Entry<Integer, String> e : gradeMap.entrySet()) {
    System.out.println(e.getKey() + " → " + e.getValue());
}
Java

このループは必ず「1 → 2 → 3」のように、キーの昇順で回ります。
HashMap のように「どの順番で出てくるか分からない」ということはありません。


TreeMap の一番おいしい部分:「キーの範囲」操作

TreeMap の真価は、「順番がある」ことで使えるメソッド群です。
ここを少し深掘りします。

最小キー・最大キーを取る(firstKey / lastKey)

TreeMap<Integer, String> map = new TreeMap<>();
map.put(10, "A");
map.put(30, "C");
map.put(20, "B");

System.out.println(map.firstKey()); // 10
System.out.println(map.lastKey());  // 30
Java

firstKey() が一番小さいキー、lastKey() が一番大きいキーです。
値もほしい場合は firstEntry() / lastEntry() で、キーと値のペアを取れます。

System.out.println(map.firstEntry()); // 10=A
System.out.println(map.lastEntry());  // 30=C
Java

あるキーの「すぐ前/すぐ後ろ」を取る(lowerKey / higherKey)

TreeMap<Integer, String> map = new TreeMap<>();
map.put(10, "A");
map.put(20, "B");
map.put(30, "C");
map.put(40, "D");

System.out.println(map.lowerKey(25));  // 20(25より小さい中で最大)
System.out.println(map.higherKey(25)); // 30(25より大きい中で最小)
Java

ある数字の「直前」「直後」を知りたいときに使えます。

スコア表、レベルの境界、料金表など、
「どのランクに属するか」を判定するロジックと非常に相性が良いです。

以下/以上の境界を含めたいとき(floorKey / ceilingKey)

System.out.println(map.floorKey(20));   // 20(20以下の最大)
System.out.println(map.floorKey(25));   // 20(25以下の最大)

System.out.println(map.ceilingKey(20)); // 20(20以上の最小)
System.out.println(map.ceilingKey(25)); // 30(25以上の最小)
Java

lower / higher は「より小さい/より大きい(=厳密な不等号)」
floor / ceiling は「以下/以上(=イコールも含む)」という違いです。

境界判定で「〇以上」「〇より大きい」を使い分けたい場面で、
この微妙な差がじわじわ効きます。

範囲でサブ Map を作る(headMap / tailMap / subMap)

TreeMap<Integer, String> map = new TreeMap<>();
map.put(10, "A");
map.put(20, "B");
map.put(30, "C");
map.put(40, "D");
map.put(50, "E");

System.out.println(map.headMap(30));    // {10=A, 20=B}        (30より小さいキー)
System.out.println(map.tailMap(30));    // {30=C, 40=D, 50=E}  (30以上のキー)
System.out.println(map.subMap(20, 50)); // {20=B, 30=C, 40=D}  (20以上50未満)
Java

「このキー範囲だけ使いたい」
「20〜49 点のユーザーだけ集計したい」

といった用途に、ほぼそのまま使えます。

subMap の第 2 引数(toKey)は「未満」であることに注意してください。
「以上/以下」含めた細かい指定が必要な場合は、subMap のオーバーロードで調整できますが、
初心者のうちは「from は含む、to は含まない」と覚えておけば十分です。


String / 自作クラスをキーにするときの「順序」の話

String をキーにすると辞書順で並ぶ

TreeMap<String, Integer> map = new TreeMap<>();
map.put("banana", 5);
map.put("apple", 3);
map.put("orange", 2);

System.out.println(map); // {apple=3, banana=5, orange=2}
Java

String はもともと「辞書順の compareTo」を持っているので、
その自然な順序でソートされます。

自作クラスをキーにするなら「並べ方」を定義する必要がある

TreeMap は「キーの大小」を比較できないと動きません。
つまり、キーにするクラスは

  • Comparable を実装して compareTo を持つ
  • あるいは TreeMap を作るときに Comparator を渡す

のどちらかが必要です。

Comparable を実装するパターン

class User implements Comparable<User> {
    String name;
    User(String name) { this.name = name; }

    @Override
    public int compareTo(User other) {
        return this.name.compareTo(other.name); // 名前の辞書順で比較
    }

    @Override
    public String toString() {
        return name;
    }
}
Java

こうしておけば、

TreeMap<User, Integer> map = new TreeMap<>();
map.put(new User("Bob"), 1);
map.put(new User("Alice"), 2);
map.put(new User("Carol"), 3);

System.out.println(map);
// {Alice=2, Bob=1, Carol=3} のように、name の辞書順で並ぶ
Java

Comparator を渡すパターン

クラス自体をいじりたくない場合は、TreeMap のコンストラクタで「比較ルール」を渡します。

TreeMap<User, Integer> map =
        new TreeMap<>((u1, u2) -> u1.name.compareTo(u2.name));
Java

これで「この TreeMap では名前の辞書順でキーを並べる」というルールになり、
User 自体は Comparable を実装していなくても構いません。

ここで大事なのは、

  • TreeMap のキーは「順序付けができる型」でなければならない
  • 自作クラスをキーにするなら、「何を基準に並べるべきか」を自分で決める必要がある

という意識です。


TreeMap を選ぶべき場面、選ばなくていい場面

TreeMap を選ぶべき場面

次のような条件があるとき、TreeMap が向いています。

  • Map(キー → 値)で管理したい
  • キーに自然な順序がある(数値の大小、文字列の辞書順など)
  • 順番や範囲がロジックに関係する

具体例でいうと、

  • テストの点数の境界(0〜59 不合格、60〜79 合格、80〜100 優秀…)を Map で持ち、点数からランクを調べたい
  • 日付や時刻をキーにして、「ある期間のデータだけ集計したい」
  • ユニークなユーザー名を辞書順に列挙したい

こういうところで TreeMap を使うと、
lowerKey / higherKey / subMap などのメソッドでロジックがすっきり書けます。

HashMap / LinkedHashMap で十分な場面

逆に、次のようなときは TreeMap をわざわざ使う必要はありません。

  • キーの順番に意味がない(存在して取れればそれでいい) → HashMap
  • 順番は「挿入順」が良い(入れた順に表示したい) → LinkedHashMap
  • キーの範囲検索や境界判定を使わない

TreeMap は内部で「木構造」を使う分、HashMap より少し重いです。
「順序・範囲を本当に使うか?」を自分に問いかけてから選ぶと、無駄が減ります。


まとめ:TreeMap を頭の中でどう位置づけるか

初心者向けに TreeMap の本質をまとめると、こうなります。

  • TreeMap は「キーでソートされた順番」を常に保つ Map。
  • 基本的な使い方(put / get / remove)は HashMap とほぼ同じ。
  • firstKey, lastKey, lowerKey, higherKey, floorKey, ceilingKey, headMap, tailMap, subMap など、順序・範囲に関する強力なメソッドがある。
  • キーに使う型は「順序を決められる」必要があり、String, Integer はそのまま使える。自作クラスなら Comparable 実装 or Comparator が必要。
  • 「キーの順番や範囲をロジックで使う」なら TreeMap、「順番どうでもいい」なら HashMap、「挿入順が欲しい」なら LinkedHashMap。

タイトルとURLをコピーしました