这是「Java 从基础到实战」系列的第 2 篇。集合是日常开发中使用率最高的 API,本文用一个「学生成绩管理」的真实场景,讲清 ArrayList、LinkedList、HashMap、HashSet 各自该在什么时候用、怎么用,并复现 3 个新手最容易踩的坑。所有代码在 JDK 17 下可直接运行。
前言
「存一堆数据该用 List 还是 Map?」「遍历时删除元素为什么会抛异常?」这些问题几乎每个 Java 新手都会遇到。本文不背八股,全部用可运行的代码说话:每种集合先给选型结论,再给场景代码,最后集中拆解常见坑。
环境准备
| 软件 | 版本 |
|---|---|
| JDK | 17(Eclipse Temurin) |
| IntelliJ IDEA | Community 社区版 |
环境搭建见上一篇:《从零搭建 Java 开发环境:JDK 17 + IntelliJ IDEA 保姆级教程》。
在 IDEA 中打开上篇创建的 java-basics 项目,右键 src 目录 → New → Package,创建包 collection,本文所有类都放在这个包下。
步骤 1:一张表看懂选型
先给结论,后面逐个验证:
| 集合 | 底层结构 | 适用场景 | 典型操作复杂度 |
|---|---|---|---|
ArrayList |
动态数组 | 按索引随机访问多、尾部追加多(默认首选) | 随机访问 O(1),中间插入/删除 O(n) |
LinkedList |
双向链表 | 头尾频繁插入删除(队列/栈场景) | 头尾插删 O(1),随机访问 O(n) |
HashMap |
数组 + 链表/红黑树 | 键值对查找(用得最多的 Map) | 存取平均 O(1) |
HashSet |
基于 HashMap | 去重、判断「是否存在」 | 增删查平均 O(1) |
💡 记住一条经验法则:列表用 ArrayList,键值对用 HashMap,去重用 HashSet,能覆盖日常 90% 的场景;只有明确遇到性能瓶颈时才考虑换实现。
步骤 2:ArrayList —— 有序列表的默认选择
场景:录入一个班级的学生名单,按顺序展示。
在 collection 包下新建 ArrayListDemo.java:
package collection; |
运行(Shift + F10),预期输出:
名单:[张三, 赵六, 李四, 王五] |
💡 如果能预估元素数量,用
new ArrayList<>(1000)指定初始容量,可避免扩容时的数组复制开销。
步骤 3:HashMap —— 键值对查找的主力
场景:根据学生姓名查成绩,这是 Map 的典型用法。
新建 HashMapDemo.java:
package collection; |
预期输出(HashMap 不保证顺序,遍历顺序可能与你不同):
张三的成绩:90 |
💡 需要按插入顺序遍历用
LinkedHashMap,需要按 key 排序用TreeMap,用法与 HashMap 完全一致,换个实现类即可。
步骤 4:HashSet —— 去重与存在性判断
场景:统计一次考试中出现过的所有分数档(自动去重)。
新建 HashSetDemo.java:
package collection; |
预期输出:
去重后的分数档:[85, 60, 92, 78] |
💡 大列表里做「是否包含」判断时,先转成 HashSet 再查,是最常见的性能优化手段之一。
步骤 5:LinkedList —— 只在队列场景考虑它
很多教程说「频繁插入删除用 LinkedList」,实际上中间位置的插入删除 LinkedList 并不快(找到位置本身就要 O(n))。它真正的价值是当**队列(Queue)/双端队列(Deque)**用:
新建 LinkedListDemo.java:
package collection; |
预期输出:
下一位:张三 |
💡 结论:日常开发中 LinkedList 出场机会很少——列表用 ArrayList,队列用 ArrayDeque。
常见坑
坑 1:遍历时删除元素,抛 ConcurrentModificationException
场景:把不及格(<60 分)的成绩从列表中移除。
错误示范(增强 for 中直接 remove)❌:
List<Integer> scores = new ArrayList<>(List.of(85, 45, 92, 30)); |
正确写法一:用迭代器的 remove ✅
Iterator<Integer> it = scores.iterator(); |
正确写法二:JDK 8+ 的 removeIf(推荐,一行搞定)✅
scores.removeIf(score -> score < 60); |
坑 2:Arrays.asList / List.of 返回的列表不能增删
错误示范 ❌:
List<String> names = Arrays.asList("张三", "李四"); |
原因:Arrays.asList 返回的是定长视图,List.of 返回的是不可变集合,都不支持增删。
正确写法:需要可变列表时,包一层 ArrayList ✅
List<String> names = new ArrayList<>(Arrays.asList("张三", "李四")); |
坑 3:自定义类作 HashMap 的 key,忘记重写 equals 和 hashCode
错误示范 ❌:
class Student { |
原因:没重写 hashCode/equals 时,比较的是对象内存地址,两个 new 出来的对象永远不相等。
正确写法:用 JDK 17 的 record,自动生成 equals/hashCode ✅
record Student(String name) {} |
普通类则用 IDEA 快捷键 Alt + Insert → equals() and hashCode() 自动生成。
小结
本文用学生成绩管理场景过了一遍最常用的四种集合:
- ✅ 列表默认用 ArrayList,队列用 ArrayDeque
- ✅ 键值查找用 HashMap,配合
getOrDefault、merge事半功倍 - ✅ 去重和存在性判断用 HashSet
- ✅ 避开三大坑:遍历中删除用
removeIf、Arrays.asList不可增删、自定义 key 必须重写equals/hashCode
下一篇我们解决集合泛型背后的疑惑:《一文吃透 Java 泛型:从 List<T> 到通配符 ? extends》,搞懂为什么 List<Integer> 不能赋值给 List<Number>,敬请期待。
上一篇:《从零搭建 Java 开发环境:JDK 17 + IntelliJ IDEA 保姆级教程》
本系列完整目录见博客「技术博文」分类,每两周更新一篇。
💬 评论