List和Set的区别

一、先明确:为什么考察 List 和 Set 的区别?

  1. 你是否能区分两者的核心特性?(有序性、重复性、索引访问)
  2. 能否掌握各自核心实现类(ArrayList/LinkedList、HashSet/TreeSet)的差异和适用场景?
  3. 能否结合实际业务说出选型依据?(比如 "存储有序数据用 List,去重数据用 Set")

二、先铺垫:List 和 Set 的通俗认知

先通过一句话建立直观印象,避免一上来就讲抽象概念:

  • List:"有序、可重复的集合,像一个'有序的抽屉',每个元素有固定位置(索引),可以存放重复的物品,还能通过位置快速找到物品";
  • Set:"无序(部分实现类有序)、不可重复的集合,像一个'去重的篮子',不能存放重复物品,也无法通过位置查找,只能判断物品是否存在"。

三、核心:List 和 Set 的维度对比

从 5 个核心维度展开对比,每个维度都搭配通俗解释,避免空洞表格:

对比维度 List 接口 Set 接口
元素重复性 允许存储重复元素(如可添加多个 "张三") 不允许存储重复元素(添加重复元素会失败,返回 false)
有序性 保证元素的「插入顺序」(有序集合),元素的存放顺序和添加顺序一致 默认无序(如 HashSet),不保证插入顺序;仅部分实现类(如 TreeSet)保证「排序顺序」(自然排序 / 自定义排序),非插入顺序
索引访问 支持索引访问(通过下标get(int index)获取元素,下标从 0 开始) 不支持索引访问(无get(int index)方法),只能通过迭代器或增强 for 循环遍历
底层实现逻辑 核心实现类(ArrayList/LinkedList)基于数组 / 链表实现,无需处理元素重复问题 核心实现类(HashSet/TreeSet)基于哈希表(HashMap)/ 红黑树(TreeMap)实现,通过 "哈希值 / 比较器" 保证元素唯一性
常用核心方法 包含索引相关方法:get(int index)add(int index, E e)remove(int index)set(int index, E e) 无索引相关方法,新增contains(Object o)方法(高频用于判断元素是否存在),添加元素方法add(E e)返回 boolean(判断是否添加成功)

四、重点:各自核心实现类解析

接口本身无法实例化,实际开发中使用的是实现类,这是考察的重点:

1. List 接口的核心实现类

1.1 ArrayList(最常用)

  • 底层结构:动态数组
  • 核心特性:查询效率高(通过索引直接访问),增删效率低(中间位置增删需移动数组元素);
  • 适用场景:业务中高频查询、少量增删的场景(如存储用户列表、商品列表,通过索引分页查询);
  • 注意点:初始容量为 10,扩容时默认扩容 1.5 倍,扩容会消耗性能(可提前指定初始容量优化)。

1.2 LinkedList

  • 底层结构:双向链表
  • 核心特性:查询效率低(需遍历链表),增删效率高(只需修改链表节点的引用,无需移动元素);
  • 适用场景:频繁增删(尤其是首尾增删)的场景(如实现队列、栈,或消息队列的消息存储);
  • 注意点:实现了 Deque 接口,提供addFirst()、addLast()、removeFirst()等首尾操作方法。

2. Set 接口的核心实现类

2.1 HashSet(最常用)

  • 底层结构:基于 HashMap 实现 (元素存储在 HashMap 的 key 中,value 为固定常量PRESENT);
  • 核心特性:无序(不保证插入顺序)、不可重复,查询 / 增删效率高(O (1) 时间复杂度);
  • 保证唯一性的原理:先通过元素的hashCode()方法计算哈希值,再通过equals()方法判断是否相等,两者都相同则视为重复元素;
  • 适用场景:无需关注顺序,只需去重的场景(如存储已选商品 ID、去重的手机号列表);
  • 注意点:存储的元素需重写hashCode()和equals()方法(否则无法正确去重,比如自定义实体类)。

2.2 TreeSet

  • 底层结构:基于 TreeMap 实现(红黑树);
  • 核心特性:有序(默认自然排序,如 Integer 升序、String 字典序;支持自定义排序(实现 Comparator 接口))、不可重复;
  • 保证唯一性 + 有序性的原理:通过元素的比较逻辑(自然排序Comparable/ 自定义排序Comparator)判断是否重复及排序;
  • 适用场景:需要去重且按指定规则排序的场景(如按价格排序的商品集合、按时间排序的日志 ID 集合);
  • 注意点:存储的元素需实现Comparable接口(或创建 TreeSet 时指定 Comparator),否则会抛出ClassCastException。

加分项

  1. 结合项目举例:"我在实训项目的用户管理模块中,用 ArrayList 存储用户列表(需要分页查询,按索引获取数据);用 HashSet 存储用户的角色 ID(需要去重,无需关注顺序);用 TreeSet 存储商品列表(需要按价格排序并去重)";
  2. 细节优化意识:"使用 ArrayList 时,我会提前指定初始容量(如new ArrayList<>(100)),避免频繁扩容损耗性能;自定义实体类存入 HashSet 时,会重写hashCode()和equals()方法,保证去重生效";
  3. 深入理解实现类:"我知道 ArrayList 底层是动态数组,查询快增删慢;LinkedList 底层是双向链表,增删快查询慢;HashSet 底层依赖 HashMap,TreeSet 底层依赖 TreeMap"。

踩坑点

  1. 混淆 Set 的有序性:误以为 "所有 Set 都是无序的",忽略 TreeSet 是有序(排序顺序)的;
  2. 自定义实体类存入 Set 未重写方法 :直接将自定义实体类存入 HashSet,未重写hashCode()equals(),导致无法去重;
  3. 滥用 ArrayList 做频繁增删:在频繁中间增删的场景下使用 ArrayList,导致性能低下(应使用 LinkedList);
  4. **试图通过索引访问 Set:**调用 Set 的get(int index)方法,导致编译报错(Set 无索引访问方法)。

举一反三

  1. "ArrayList 和 LinkedList 的核心区别是什么?各自的适用场景是什么?"(答案:底层结构不同(数组 / 双向链表);ArrayList 查询快、增删慢,适用于查询多的场景;LinkedList 增删快、查询慢,适用于增删多的场景);
  2. "HashSet 是如何保证元素唯一性的?如果两个元素的 hashCode 相同但 equals 不同,会发生什么?"(答案:通过hashCode()+equals()判断;hashCode 相同但 equals 不同,会在哈希表的同一桶位形成链表(JDK8 后链表长度超过 8 转为红黑树),仍能存储为不同元素);
  3. "如何让 TreeSet 按自定义规则排序(比如按用户年龄倒序)?"(答案:1. User 类实现Comparable<User>接口,重写compareTo方法;2. 创建 TreeSet 时传入Comparator<User>匿名内部类 / Lambda 表达式,指定排序规则);
  4. "List 和 Set 都继承自 Collection 接口,它们有哪些共同的方法?"(答案:add(E e)、remove(Object o)、contains(Object o)、size()、isEmpty()、clear()等)。
相关推荐
JxWang054 分钟前
Task04:字符串
后端
树獭叔叔35 分钟前
10-让模型更小更聪明,学而不忘:知识蒸馏与持续学习
后端·aigc·openai
JxWang0543 分钟前
Task02:链表
后端
只会cv的前端攻城狮2 小时前
Elpis-Core — 融合 Koa 洋葱圈模型实现服务端引擎
前端·后端
codetown2 小时前
2026年Zig编程语言权威指南:从系统级底层架构到现代软件工程实践
后端·程序员
cg333 小时前
cc-connect,十分钟帮你把 claude code 连接到微信,飞书,钉钉等等平台
后端·openai
用户1427868669323 小时前
Java多态的底层真相:JVM到底怎么知道该调哪个方法?(面试高频)
后端
初次攀爬者4 小时前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
摸鱼的春哥4 小时前
惊!黑客靠AI把墨西哥政府打穿了,海量数据被黑
前端·javascript·后端
考虑考虑4 小时前
JDK25模块导入声明
java·后端·java ee