HashSet适合去重快查,TreeSet适合排序或范围操作;TreeSet存null抛NullPointerException,HashSet允许一个null;自定义类入TreeSet需实现Comparable或传Comparator;HashSet去重须同时重写hashCode()和equals()且逻辑一致;TreeSet的subSet()等返回高效视图,共享数据。
HashSet 适合去重快查,TreeSet 适合要排序或范围操作的场景——选错一个,线上可能慢几倍。
TreeSet 在插入时必须比较元素大小,而 null 无法参与比较(compareTo() 或 compare() 都会直接抛 NullPointerException)。HashSet 没这个问题,它只调用 hashCode() 和 equals(),null 是合法值(且只能存一个)。
null → 运行时报错,不是编译错误
null → 合法,但后续再 add null 会失败(返回 false)Comparable,要么构造时传 Comparator,否则运行时报 ClassCastException
必须同时重写 hashCode() 和 equals(),且逻辑一致:如果 equals() 返回 true,两个对象的 hashCode() 必须相等。
public class User {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 和 equals 用的字段完全一致
}
}
equals() 不重 hashCode() → HashSet 当成不同对象,重复添加成功hashCode() → 对象修改后可能从 HashSet 中“消失”(查不到)是的。这些方法返回的是底层红黑树的**视图(view)**,不是新集合,不复制数据,时间复杂度 O(log n),空间 O(1)。但要注意:视图和原 TreeSet 共享数据,修改视图会影响原集。
treeSet.subSet(5, true, 10, false) → 左闭右开区间 [5, 10)NavigableSet 仍支持 first()、last()、higher() 等操作ConcurrentModificationException
最常被忽略的一点:TreeSet 的排序依赖于比较逻辑的稳定性——如果比较器内部用了可变字段,或者 compareTo() 实现违反了自反性/传递性,集合行为将不可预测,甚至导致 add() 卡死或返回错误结果。