在Java里HashSet和TreeSet有什么区别_JavaSet实现对比说明

HashSet适合去重快查,TreeSet适合排序或范围操作;TreeSet存null抛NullPointerException,HashSet允许一个null;自定义类入TreeSet需实现Comparable或传Comparator;HashSet去重须同时重写hashCode()和equals()且逻辑一致;TreeSet的subSet()等返回高效视图,共享数据。

HashSet 适合去重快查,TreeSet 适合要排序或范围操作的场

景——选错一个,线上可能慢几倍。

为什么 add() 会抛 NullPointerException?

TreeSet 在插入时必须比较元素大小,而 null 无法参与比较(compareTo()compare() 都会直接抛 NullPointerException)。HashSet 没这个问题,它只调用 hashCode()equals()null 是合法值(且只能存一个)。

  • TreeSet 中存 null → 运行时报错,不是编译错误
  • HashSet 中存 null → 合法,但后续再 add null 会失败(返回 false
  • 自定义类想进 TreeSet,要么实现 Comparable,要么构造时传 Comparator,否则运行时报 ClassCastException

怎么让自定义对象在 HashSet 里不重复?

必须同时重写 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 中“消失”(查不到)

TreeSet 的 subSet()、headSet() 真的高效吗?

是的。这些方法返回的是底层红黑树的**视图(view)**,不是新集合,不复制数据,时间复杂度 O(log n),空间 O(1)。但要注意:视图和原 TreeSet 共享数据,修改视图会影响原集。

  • treeSet.subSet(5, true, 10, false) → 左闭右开区间 [5, 10)
  • 返回的 NavigableSet 仍支持 first()last()higher() 等操作
  • 如果原 TreeSet 被并发修改(非线程安全),视图操作可能抛 ConcurrentModificationException

最常被忽略的一点:TreeSet 的排序依赖于比较逻辑的稳定性——如果比较器内部用了可变字段,或者 compareTo() 实现违反了自反性/传递性,集合行为将不可预测,甚至导致 add() 卡死或返回错误结果。