TreeSet 通过底层 TreeMap 和元素的 compareTo() 方法实现自然排序,要求元素实现 Comparable 接口;否则 add 时抛 ClassCastException,且 compareTo() 必须与 equals() 逻辑一致,否则引发严重 bug。

TreeSet 本身就会按元素自然顺序自动排序,不需要额外操作——前提是元素类型实现了 Comparable 接口,且你没传入自定义 Comparator。
为什么 new TreeSet() 就能自动排序?
TreeSet 底层是 TreeMap,它在插入时依赖 compareTo() 方法比较大小。只要元素类(如 Integer、String、LocalDate)已实现 Comparable,构造空参 TreeSet 就会启用自然顺序。
- 若元素未实现
Comparable(比如自定义类Person),add 时会抛ClassCastException - 传入
Comparator构造器(如new TreeSet(Comparator.reverseOrder()))会覆盖自然顺序 -
null值不允许插入(除非用支持 null 的定制比较器,但标准自然顺序下不支持)
自定义类必须实现 Comparable 才能进 TreeSet
比如想让 Book 按 isbn 自然排序,不能只靠重写 toString() 或 equals():
public class Book implements Comparable<Book> {
private String isbn;
// ...
@Override
public int compareTo(Book other) {
return this.isbn.compareTo(other.isbn); // 依赖 String 自带的 compareTo
}
}
- 必须重写
compareTo(),返回负数/0/正数表示小于/等于/大于 - 比较逻辑要与
equals()保持一致(即:若a.equals(b)为 true,则a.compareTo(b) == 0) - 若字段可能为
null,需手动判空,否则 NPE;推荐用Objects.compare(a, b, Comparator.nullsLast(String::compareTo))
注意 add() 和 contains() 的行为一致性
TreeSet 的 contains() 判断是否“存在”,完全依赖 compareTo() 结果是否为 0,而不是 equals():
- 如果
compareTo()认为两个对象相等(返回 0),即使equals()返回 false,add()也会拒绝重复插入 - 反过来说,若
compareTo()返回非零,但equals()返回 true,TreeSet 会把它们当不同元素处理——这违反约定,极易引发逻辑 bug - 所以:只要用了自然排序,
compareTo()和equals()的等价逻辑必须对齐
性能和线程安全不是默认选项
TreeSet 是非同步的,且每次 add() / contains() 都是 O(log n) 时间复杂度:
- 别把它当
HashSet用——如果你只关心去重不关心顺序,HashSet更快 - 多线程环境下直接并发修改会抛
ConcurrentModificationException,需外层同步或改用Collections.synchronizedSortedSet(new TreeSet()) - 迭代顺序始终是升序;降序需用
treeSet.descendingSet(),它返回的是视图,不是新集合
真正容易被忽略的是:自然顺序的“自然”二字,完全绑定在 Comparable 实现上——删掉 compareTo() 或写错返回值,TreeSet 不会报编译错误,但排序结果会彻底失序,而且问题往往在数据量变大后才暴露。

















