Set接口保证元素不重复但不保证顺序;HashSet依赖hashCode()和equals()去重,需同步重写且避免可变字段;TreeSet要求元素可比较,否则运行时抛ClassCastException;LinkedHashSet保持插入顺序但内存开销更大。

Set 接口保证元素不重复,但不保证顺序
Java 的 Set 接口核心语义就是「无序、不重复」。它不继承 List,所以没有索引访问(get(int))、不能存重复元素(add() 返回 false 表示已存在),也不承诺遍历顺序——这点和 HashSet、LinkedHashSet、TreeSet 的具体实现强相关。
HashSet 用哈希码去重,但要求正确重写 equals() 和 hashCode()
最常用的实现 HashSet 依赖对象的 hashCode() 和 equals() 判断是否重复。如果只重写 equals() 却没同步改 hashCode(),会出现「两个逻辑相等的对象能同时加进 Set」的诡异现象。
常见错误场景:
- 自定义类(如
Person)没重写hashCode(),仅靠equals()判断相等 →Set无法识别重复 - 重写了
hashCode()但用了可变字段(比如name可被修改),之后又修改该字段 → 对象可能从Set中「消失」,再也contains()不到
public class Person {
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;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 用相同字段
}
}
TreeSet 要求元素可比较,否则运行时报 ClassCastException
TreeSet 是基于红黑树的有序 Set,它要么要求元素实现 Comparable 接口(如 String、Integer),要么在构造时传入 Comparator。否则往里加对象会直接抛 ClassCastException,不是编译错,是运行时错。
立即学习“Java免费学习笔记(深入)”;
典型踩坑点:
- 往空的
TreeSet里 add 自定义对象,但Person没实现Comparable,也没传Comparator - 用了
Comparator,但比较逻辑返回 0 的条件和equals()不一致 →Set去重行为和预期不符
LinkedHashSet 记住插入顺序,但内存开销略大
LinkedHashSet 在 HashSet 基础上加了双向链表,所以迭代顺序 = 插入顺序。适合需要「去重 + 保持添加顺序」的场景,比如去重日志、过滤重复请求 ID 并按时间排列。
但它比 HashSet 多维护链表指针,每个元素多占约 8–16 字节内存;插入和删除也稍慢一点。如果不需要顺序,别默认选它。
注意:LinkedHashSet 的「顺序」只反映插入顺序,和 hashCode 或内容无关;它也不支持按访问顺序排序(那是 LinkedHashMap 的事)。
hashCode(),HashSet 就失效;而 TreeSet 的「重复」判断甚至和 equals() 无关,只看 compareTo() 是否返回 0。










