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年}
}
}
Javaput, 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
JavafirstKey() が一番小さいキー、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以上の最小)
Javalower / 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}
JavaString はもともと「辞書順の 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 の辞書順で並ぶ
JavaComparator を渡すパターン
クラス自体をいじりたくない場合は、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実装 orComparatorが必要。 - 「キーの順番や範囲をロジックで使う」なら TreeMap、「順番どうでもいい」なら HashMap、「挿入順が欲しい」なら LinkedHashMap。
