Set集合通过以下机制确保元素不重复:
一、Set接口的设计原则
Set接口继承自Collection,强制要求实现类不允许包含重复元素。其核心约束为:
- 唯一性:每个元素在Set中只能出现一次。
- 无索引 :不提供基于索引的访问方法(如
get(int index)
)。
二、实现类机制解析
不同的Set实现类通过不同方式确保元素唯一性:
1. HashSet(基于哈希表)
-
底层结构 :基于
HashMap
实现,元素作为HashMap的键存储。 -
唯一性判断:
- 哈希码(hashCode) :调用元素的
hashCode()
方法确定存储位置。 - 相等性(equals) :哈希冲突时,调用
equals()
方法比较元素内容。
- 哈希码(hashCode) :调用元素的
-
添加流程:
- 计算元素哈希值,定位到哈希桶。
- 若桶为空,直接存入。
- 若桶非空,遍历链表/红黑树,通过
equals()
比较是否存在重复元素。 - 若存在重复,丢弃新元素;否则插入链表/树。
-
代码示例:
typescriptjava public boolean add(E e) { return map.put(e, PRESENT) == null; // HashMap的键唯一 }
2. TreeSet(基于红黑树)
-
底层结构 :基于
TreeMap
实现,元素按自然顺序或Comparator排序。 -
唯一性判断:
- 比较结果(compareTo/compare) :通过
Comparable
或Comparator
比较元素。 - 相等性定义 :当
compareTo()
或compare()
返回0时,视为重复。
- 比较结果(compareTo/compare) :通过
-
添加流程:
- 通过比较器确定元素位置。
- 若比较结果为0,替换或丢弃元素(根据实现逻辑)。
- 若比较结果非0,插入到合适位置以维持排序。
-
注意事项:
- 与equals()的一致性 :若
compareTo()
与equals()
逻辑不一致,可能导致元素被视为重复但equals返回false。
- 与equals()的一致性 :若
3. LinkedHashSet(有序哈希集合)
- 底层结构:继承自HashSet,通过双向链表维护插入顺序。
- 唯一性判断:与HashSet相同(依赖hashCode和equals)。
三、关键方法的作用
-
hashCode()
- 确定元素在哈希表中的存储位置。
- 若哈希码冲突,需进一步通过
equals()
判断是否重复。
-
equals()
- 精确判断两个对象是否逻辑相等。
- 必须满足自反性、对称性、传递性和一致性。
-
compareTo() / compare()
- 在TreeSet中定义元素的排序和唯一性。
- 返回0时视为重复,无论
equals()
结果如何。
四、用户自定义类的注意事项
-
重写hashCode()和equals()
-
规则 :若
a.equals(b)
为true,则a.hashCode() == b.hashCode()
必须成立。 -
示例:
typescriptjava public class Person { private String name; private int age; @Override public int hashCode() { return Objects.hash(name, age); // 基于属性生成哈希码 } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && Objects.equals(name, person.name); } }
-
-
实现Comparable或提供Comparator
-
TreeSet要求 :元素必须可比较,否则抛出
ClassCastException
。 -
示例:
csharpjava // 自然排序 public class Person implements Comparable<Person> { @Override public int compareTo(Person other) { return this.name.compareTo(other.name); } } // 定制Comparator TreeSet<Person> set = new TreeSet<>(Comparator.comparingInt(Person::getAge));
-
五、常见问题与解决方案
问题场景 | 原因 | 解决方案 |
---|---|---|
HashSet添加重复元素 | 未正确重写hashCode和equals方法 | 检查并正确实现hashCode()和equals() |
TreeSet抛出ClassCastException | 元素未实现Comparable且无Comparator | 提供Comparator或实现Comparable接口 |
比较逻辑与equals不一致 | compareTo()与equals()结果冲突 | 确保两者逻辑一致 |
六、总结
- HashSet :依赖哈希表和
equals()/hashCode()
,适合快速查找。 - TreeSet:依赖红黑树和比较器,适合有序遍历。
- LinkedHashSet:在HashSet基础上维护插入顺序。
- 核心原则:正确实现元素的哈希码、相等性及比较逻辑,确保Set正确识别重复元素。
七、注意事项
- 哈希冲突≠重复元素:哈希值相同但内容不同的对象会被放到同一桶(链表或树中)。
- 不可变对象更安全:若对象存入Set后发生修改,可能导致哈希值变化,引发内存泄漏或重复元素。
- 线程不安全 :多线程操作需用
Collections.synchronizedSet()
或ConcurrentHashMap.newKeySet()
包装。