TreeMap默认按键的自然顺序排序,要求键类型实现Comparable接口,否则抛ClassCastException;可让类实现Comparable或传入Comparator,后者优先级更高且更灵活;注意null处理、线程安全及性能差异。

TreeMap用Comparable排序时,类必须自己实现接口
TreeMap默认按键的自然顺序排序,前提是键类型实现了Comparable接口。如果你传入一个没实现它的自定义类(比如Person),运行时会抛出ClassCastException,错误信息类似:java.lang.ClassCastException: Person cannot be cast to java.lang.Comparable。
解决方法是让类直接实现Comparable,并在compareTo()里写清楚比较逻辑:
public class Person implements Comparable<Person> {
private String name;
private int age;
public int compareTo(Person p) {
int nameCmp = this.name.compareTo(p.name);
return nameCmp != 0 ? nameCmp : Integer.compare(this.age, p.age);
}
}
注意点:
-
compareTo()返回负数、0、正数分别表示“小于”“等于”“大于”,不能只返回-1/0/1 - 如果字段可能为
null,要用Objects.compare()或手动判空,否则NPE - 实现后,构造
TreeMap<Person, String>就能直接用,无需额外参数
用Comparator构造TreeMap更灵活,适合复用或临时逻辑
当不想改原有类(比如第三方类),或需要多种排序方式(按年龄升序、按姓名降序等),就该用Comparator。它通过构造函数传入,优先级高于Comparable。
立即学习“Java免费学习笔记(深入)”;
常见写法:
// 匿名内部类(老写法,略冗长)
TreeMap<Person, String> map = new TreeMap<>(new Comparator<Person>() {
public int compare(Person a, Person b) {
return Integer.compare(a.getAge(), b.getAge());
}
});
// Lambda(推荐,JDK 8+)
TreeMap<Person, String> map = new TreeMap<>((a, b) -> a.getName().compareTo(b.getName()));
关键细节:
- 传入的
Comparator对象在TreeMap整个生命周期内被复用,不能依赖外部可变状态 - 如果Comparator逻辑复杂,建议提取成静态常量,避免每次new:比如
public static final Comparator<Person> BY_AGE = Comparator.comparingInt(Person::getAge); - 用
Comparator.nullsFirst()或Comparator.nullsLast()显式处理null,别指望默认行为
两种方式混用时,Comparator永远覆盖Comparable
如果一个类实现了Comparable,但你又给TreeMap传了Comparator,那么Comparable完全被忽略——TreeMap只认构造时传入的那个比较器。
这在调试时容易误判:比如你看到类写了compareTo(),但实际排序结果不符合预期,第一反应不该怀疑实现有没有错,而是先检查TreeMap是不是被传了别的Comparator。
典型陷阱:
- 从Spring等框架注入的
TreeMapbean,可能已在配置中指定了Comparator,代码里却没意识到 - 测试时用无参构造,生产环境用了带Comparator的构造,行为不一致
- Comparator里用了未初始化的字段(比如
this.config为null),导致NullPointerException发生在put操作时,而非构造时
性能和线程安全:TreeMap不是万能替代品
TreeMap底层是红黑树,所有操作都是O(log n),比HashMap的O(1)慢。如果你只是想“保持插入顺序”,用LinkedHashMap更合适;如果根本不需要排序,HashMap就是首选。
另外,TreeMap不是线程安全的。多线程写入时,不能靠加锁包装,得用Collections.synchronizedSortedMap()或直接换ConcurrentSkipListMap(它支持并发且保持排序)。
容易被忽略的一点:TreeMap的subMap()、headMap()等视图方法返回的是原Map的动态视图,修改视图会直接影响原Map,且这些视图本身也不具备线程安全性。










