057_泛型类 / 泛型方法 / 泛型接口
一、Set接口概述
Set是Java集合框架中无序、不可重复的集合接口,继承自Collection接口。与List不同,Set不允许存储重复元素(通过元素的equals()方法判断重复),且没有索引概念(不支持通过下标访问元素)。
Set接口的核心实现类包括HashSet、TreeSet和LinkedHashSet,它们在底层结构、有序性、去重机制和性能上存在显著差异,适用于不同的业务场景。
二、HashSet详解
2.1 底层结构与核心原理
HashSet是Set接口最常用的实现类,底层基于哈希表(HashMap) 实现——通过HashMap的key存储元素(value为固定空对象)。其核心特点是利用哈希表的特性实现快速查找和去重。
- 去重机制:添加元素时,先通过元素的hashCode()方法计算哈希值,定位到哈希表中的桶位置;再通过equals()方法比较桶中元素,若两者均相等则视为重复元素,拒绝添加。
- 初始容量与加载因子:默认初始容量为16,加载因子为0.75(当元素数量超过容量×加载因子时,触发扩容,容量翻倍)。
建议插入图片位置:此处可插入一张“HashSet底层哈希表示意图”,展示哈希表的桶结构,元素通过哈希值定位到桶,桶中无重复元素,标注“hashCode()定位桶”“equals()去重”的核心逻辑,直观呈现去重原理。
2.2 核心特点
2.3 常用方法示例
import java.util.HashSet;import java.util.Set;public class HashSetDemo { public static void main(String[] args) { Set<String> set = new HashSet<>(); // 添加元素(重复元素会被过滤) set.add(\"Apple\"); set.add(\"Banana\"); set.add(\"Apple\"); // 重复元素,添加失败 System.out.println(\"集合元素:\" + set); // [Apple, Banana](顺序可能不同) // 判断元素是否存在 boolean contains = set.contains(\"Banana\"); System.out.println(\"是否包含Banana:\" + contains); // true // 删除元素 set.remove(\"Apple\"); System.out.println(\"删除后元素:\" + set); // [Banana] // 遍历元素(无固定顺序) for (String s : set) { System.out.println(s); } }}
2.4 注意事项与适用场景
- 元素必须重写hashCode()和equals():否则无法正确去重(默认使用Object的方法,仅比较地址)。重写规则:equals()相等的元素,hashCode()必须相等;hashCode()相等的元素,equals()可不等(哈希冲突)。
- 适用场景:需快速去重、查询频繁且不关注元素顺序的场景(如存储用户ID、过滤重复数据)。
三、TreeSet详解
3.1 底层结构与核心原理
TreeSet是基于红黑树(自平衡二叉查找树) 实现的Set接口,其核心特点是元素有序且不可重复。红黑树通过节点间的比较关系维持有序性,保证增删查操作的时间复杂度为O(log n)。
- 有序性实现:元素需实现Comparable接口(自然排序),或通过构造器传入Comparator比较器(定制排序),通过compareTo()或compare()方法确定元素顺序。
- 去重机制:若比较结果为0(表示两元素相等),则视为重复元素,拒绝添加(无需依赖hashCode()和equals())。
建议插入图片位置:此处可插入一张“TreeSet底层红黑树示意图”,展示红黑树的节点结构,节点按比较规则有序排列(如从小到大),标注“左小右大”的二叉查找树特性和“平衡调整”机制,直观呈现有序存储的原理。
3.2 核心特点
3.3 常用方法示例
示例1:自然排序(元素实现Comparable)
import java.util.TreeSet;import java.util.Set;// 元素类实现Comparable接口(自然排序)class Student implements Comparable<Student> { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } // 按年龄排序 @Override public int compareTo(Student o) { return this.age - o.age; // 正数:当前元素大;0:相等;负数:当前元素小 } @Override public String toString() { return name + \"(\" + age + \")\"; }}public class TreeSetDemo1 { public static void main(String[] args) { Set<Student> set = new TreeSet<>(); set.add(new Student(\"Alice\", 20)); set.add(new Student(\"Bob\", 18)); set.add(new Student(\"Charlie\", 22)); // 迭代顺序为按年龄排序后的结果 for (Student s : set) { System.out.println(s); // Bob(18) → Alice(20) → Charlie(22) } }}
示例2:定制排序(传入Comparator)
import java.util.TreeSet;import java.util.Comparator;public class TreeSetDemo2 { public static void main(String[] args) { // 按字符串长度排序(定制排序) Set<String> set = new TreeSet<>(Comparator.comparingInt(String::length)); set.add(\"apple\"); // 5 set.add(\"banana\"); // 6 set.add(\"pear\"); // 4 for (String s : set) { System.out.println(s); // pear(4) → apple(5) → banana(6) } }}
3.4 适用场景
- 需要有序存储的场景(如排行榜、按规则排序的数据集)。
- 频繁需要范围查询的场景(如获取小于/大于某值的元素,通过subSet()等方法实现)。
- 元素可通过比较规则定义相等性的场景。
四、LinkedHashSet详解
4.1 底层结构与核心原理
LinkedHashSet是HashSet的子类,底层基于哈希表+双向链表实现。哈希表保证元素快速查找和去重,双向链表记录元素的插入顺序,因此兼具HashSet的查询效率和LinkedList的插入顺序维护特性。
- 结构继承:继承HashSet,内部通过LinkedHashMap实现(哈希表+链表),链表记录元素插入的先后关系。
- 性能特点:查询效率与HashSet接近,但因维护链表额外开销,插入和删除性能略低;迭代顺序为元素的插入顺序。
建议插入图片位置:此处可插入一张“LinkedHashSet底层结构示意图”,展示哈希表的桶结构,同时通过双向链表将元素按插入顺序连接,标注“哈希表:快速查询”“双向链表:维护插入顺序”的双重特性,直观呈现有序+高效的结合。
4.2 核心特点
4.3 常用方法示例
import java.util.LinkedHashSet;import java.util.Set;public class LinkedHashSetDemo { public static void main(String[] args) { Set<String> set = new LinkedHashSet<>(); // 添加元素(维护插入顺序) set.add(\"Java\"); set.add(\"Python\"); set.add(\"C++\"); set.add(\"Java\"); // 重复元素,添加失败 // 迭代顺序与插入顺序一致 System.out.println(\"集合元素:\" + set); // [Java, Python, C++] // 其他方法与HashSet一致 set.remove(\"Python\"); System.out.println(\"删除后元素:\" + set); // [Java, C++] }}
4.4 适用场景
- 需要有序且去重的场景(如日志记录、历史记录追踪)。
- 迭代频率高且需保持插入顺序的场景(避免HashSet的随机迭代顺序)。
- 既需要快速查询,又需要顺序访问的场景。
五、HashSet、TreeSet、LinkedHashSet对比
建议插入图片位置:此处可插入一张“三者核心特性对比表图”,将上述维度以表格形式可视化,用不同颜色标注各实现类的优势领域(如HashSet的“查询高效”、TreeSet的“有序存储”),帮助快速选择适用类。
六、使用注意事项
-
去重逻辑一致性:
- HashSet和LinkedHashSet需确保元素重写hashCode()和equals(),且两者逻辑一致(equals()相等则hashCode()必须相等)。
- TreeSet需保证排序逻辑与equals()一致(避免比较结果为0但equals()不等的情况,否则可能出现逻辑矛盾)。
-
线程安全处理:三者均为线程不安全,多线程环境下可通过Collections.synchronizedSet(new HashSet())包装,或使用ConcurrentSkipListSet(线程安全的有序Set)。
-
元素不可变性:存储在Set中的元素应尽量不可变(如String、基本类型包装类),若元素属性修改可能导致哈希值变化或排序异常,破坏Set的一致性。
-
初始容量设置:HashSet和LinkedHashSet可通过构造器指定初始容量和加载因子(如new HashSet(100, 0.75f)),减少扩容次数提升性能。
七、总结
Set接口的三大实现类各有侧重,选择时需根据核心需求判断:
- 无需有序,追求查询效率 → HashSet(日常开发首选)。
- 需要有序存储或范围查询 → TreeSet(依赖排序规则)。
- 需要保持插入顺序且去重 → LinkedHashSet(平衡有序与效率)。
理解三者的底层结构和特性,能帮助在去重、有序性、性能之间做出最优选择,提升集合操作的效率和代码可读性。