集合框架概述
本章定位:构建 Java 集合框架的整体认知,掌握 Collection 和 Map 两大体系的核心接口、通用方法和迭代器机制,为深入学习各具体实现类奠定基础。
为什么需要集合框架
在 Java 中,数组可以存储多个元素,但数组有明显的局限性:
| 对比维度 | 数组 | 集合 |
|---|---|---|
| 容量 | 固定,创建后不可变 | 动态扩容,自动增长 |
| 元素操作 | 无内置方法(需手写循环) | 丰富的 API:add/remove/contains 等 |
| 类型安全 | 仅运行时检查 | 编译期泛型检查 |
| 数据结构 | 仅线性连续内存 | 数组、链表、哈希表、树等多种结构 |
| 算法支持 | 需自行实现 | Collections/Arrays 工具类内置 |
集合框架是 Java 提供的数据结构与算法工具库,让开发者专注于业务逻辑而非底层数据结构实现。
Collection 体系总览
核心关系:
Iterable是集合框架的顶层接口,提供迭代器能力Collection继承Iterable,定义集合的通用操作List、Set、Queue是 Collection 的三大子接口Deque继承Queue,支持双端操作
Map 体系总览
核心关系:
Map独立于Collection体系,存储键值对SortedMap提供按键排序的能力NavigableMap扩展了导航方法(lowerKey、floorKey 等)HashMap是最常用的 Map 实现,Hashtable是遗留的线程安全版本
Collection 接口通用方法速查
| 方法 | 返回值 | 描述 |
|---|---|---|
add(E e) | boolean | 添加元素,成功返回 true |
remove(Object o) | boolean | 移除第一个匹配元素 |
contains(Object o) | boolean | 判断是否包含指定元素 |
size() | int | 返回集合元素个数 |
isEmpty() | boolean | 判断集合是否为空 |
clear() | void | 移除所有元素 |
iterator() | Iterator<E> | 返回迭代器 |
toArray() | Object[] | 转换为数组 |
addAll(Collection c) | boolean | 添加另一集合的全部元素 |
removeAll(Collection c) | boolean | 移除与另一集合交集的元素 |
retainAll(Collection c) | boolean | 保留与另一集合交集的元素 |
Iterator 迭代器
核心原理
Iterator 是遍历集合的标准方式,它将遍历行为与集合的底层数据结构解耦。无论底层是数组还是链表,上层都可以用统一的方式遍历。
迭代器使用示例
import java.util.*;
// 场景:飞翔科技技术部白歌审查小崔的员工列表遍历代码
public class IteratorDemo {
public static void main(String[] args) {
Collection<String> employees = new ArrayList<>();
employees.add("大翔");
employees.add("白歌");
employees.add("小崔");
employees.add("孔蓝");
// === 方式一:显式使用 Iterator ===
Iterator<String> it = employees.iterator();
while (it.hasNext()) {
String name = it.next();
System.out.println("员工: " + name);
// 安全删除:在迭代过程中删除元素
if ("小崔".equals(name)) {
it.remove(); // 使用 Iterator 的 remove()
}
}
System.out.println("删除小崔后: " + employees);
// 输出: [大翔, 白歌, 孔蓝]
// === 方式二:增强 for 循环(语法糖,底层仍是 Iterator) ===
for (String name : employees) {
System.out.println("剩余员工: " + name);
}
}
}
运行输出:
员工: 大翔
员工: 白歌
员工: 小崔
员工: 孔蓝
删除小崔后: [大翔, 白歌, 孔蓝]
剩余员工: 大翔
剩余员工: 白歌
剩余员工: 孔蓝
fail-fast 机制
什么时 fail-fast
fail-fast(快速失败)是 Java 集合框架的一种错误检测机制。当多个线程同时对集合进行结构性修改时,或单线程在迭代过程中直接修改集合(非迭代器方式),集合会立即抛出 ConcurrentModificationException,而不是让程序在不确定的状态下继续运行。
原理:modCount
每个集合内部维护一个 modCount 计数器,记录结构性修改的次数(增删操作):
// ArrayList 内部结构(简化)
public class ArrayList<E> {
// 每次 add/remove 等结构性修改都会 modCount++
protected transient int modCount = 0;
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
modCount++; // 结构性修改
return true;
}
}
迭代器在创建时会记录当前的 modCount 值(存入 expectedModCount),每次调用 next() 时检查两者是否一致:
// ArrayList 内部迭代器(简化)
private class Itr implements Iterator<E> {
int expectedModCount = modCount; // 创建时快照
public E next() {
checkForComodification(); // 每次 next 前检查
// ... 实际迭代逻辑
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
增强 for 循环与迭代器
增强 for 循环(for-each)是 Java 5 引入的语法糖,编译后等价于 Iterator 遍历:
// 源代码(增强 for)
for (String name : employees) {
System.out.println(name);
}
// 编译后等价于(反编译验证)
Iterator<String> var2 = employees.iterator();
while (var2.hasNext()) {
String name = var2.next();
System.out.println(name);
}
使用建议:
| 场景 | 推荐方式 |
|---|---|
| 仅遍历,不修改 | 增强 for 循环(代码更简洁) |
| 遍历中需要删除元素 | 显式 Iterator + it.remove() |
| 遍历中需要修改元素 | 显式 Iterator + it.next() + 修改对象属性 |
| 需要索引访问 | 普通 for 循环(List 专属) |
完整示例:飞翔科技的部门员工管理
场景简述
飞翔科技技术部小崔正在重构员工管理系统。架构师白歌要求他先理解集合框架的整体体系,再根据实际业务场景选择合适的集合类。
示例一:使用 Collection 通用方法管理员工
import java.util.*;
public class EmployeeManager {
public static void main(String[] args) {
// 使用 ArrayList 作为 Collection 的实现
Collection<String> employees = new ArrayList<>();
// 1. 添加员工
employees.add("大翔(CEO)");
employees.add("白歌(架构师)");
employees.add("小崔(后端开发)");
employees.add("孔蓝(测试工程师)");
System.out.println("当前员工总数: " + employees.size());
// 2. 判断是否包含某员工
System.out.println("是否包含白歌? " + employees.contains("白歌(架构师)"));
// 3. 批量添加
Collection<String> newHires = Arrays.asList("张三(前端)", "李四(运维)");
employees.addAll(newHires);
System.out.println("入职后总人数: " + employees.size());
// 4. 使用迭代器安全删除
Iterator<String> it = employees.iterator();
while (it.hasNext()) {
String emp = it.next();
if (emp.contains("张三")) {
it.remove(); // 安全删除
}
}
// 5. 转换为数组
Object[] empArray = employees.toArray();
System.out.println("--- 当前员工列表 ---");
for (Object obj : empArray) {
System.out.println(" " + obj);
}
// 6. 清空测试
// employees.clear();
// System.out.println("清空后: " + employees.isEmpty());
}
}
运行输出:
当前员工总数: 4
是否包含白歌? true
入职后总人数: 6
--- 当前员工列表 ---
大翔(CEO)
白歌(架构师)
小崔(后端开发)
孔蓝(测试工程师)
李四(运维)
示例二:Map 体系的部门映射
import java.util.*;
public class DepartmentMapping {
public static void main(String[] args) {
// 部门编号 → 部门名称映射
Map<Integer, String> deptMap = new HashMap<>();
deptMap.put(1001, "技术部");
deptMap.put(1002, "产品部");
deptMap.put(1003, "市场部");
deptMap.put(1004, "人事部");
// 遍历方式一:通过 keySet 遍历
System.out.println("=== 方式一:keySet 遍历 ===");
for (Integer deptId : deptMap.keySet()) {
System.out.println("部门编号: " + deptId + " → " + deptMap.get(deptId));
}
// 遍历方式二:通过 entrySet 遍历(推荐,效率更高)
System.out.println("\n=== 方式二:entrySet 遍历(推荐) ===");
for (Map.Entry<Integer, String> entry : deptMap.entrySet()) {
System.out.println("部门编号: " + entry.getKey() + " → " + entry.getValue());
}
// 遍历方式三:values 遍历
System.out.println("\n=== 方式三:values 遍历 ===");
for (String deptName : deptMap.values()) {
System.out.println("部门名称: " + deptName);
}
}
}
运行输出:
=== 方式一:keySet 遍历 ===
部门编号: 1001 → 技术部
部门编号: 1002 → 产品部
部门编号: 1003 → 市场部
部门编号: 1004 → 人事部
=== 方式二:entrySet 遍历(推荐) ===
部门编号: 1001 → 技术部
部门编号: 1002 → 产品部
部门编号: 1003 → 市场部
部门编号: 1004 → 人事部
=== 方式三:values 遍历 ===
部门名称: 技术部
部门名称: 产品部
部门名称: 市场部
部门名称: 人事部
易错场景
反例一:增强 for 循环中直接删除元素
小崔最初写的代码在增强 for 中直接调用集合的 remove() 方法:
// ❌ 错误:增强 for 中直接调用集合的 remove()
List<String> employees = new ArrayList<>();
employees.add("大翔");
employees.add("白歌");
employees.add("小崔");
for (String name : employees) {
if ("小崔".equals(name)) {
employees.remove(name); // ConcurrentModificationException!
}
}
报错信息:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
...
原理分析:增强 for 编译后是 Iterator 遍历,Iterator 的 checkForComodification() 检测到 modCount != expectedModCount,因为 employees.remove() 修改了 modCount 但 Iterator 的 expectedModCount 未更新。
纠正方案:
// ✅ 正确:使用 Iterator 的 remove()
Iterator<String> it = employees.iterator();
while (it.hasNext()) {
String name = it.next();
if ("小崔".equals(name)) {
it.remove(); // Iterator 会同步 expectedModCount = modCount
}
}
// ✅ 正确(Java 8):使用 removeIf() 方法
employees.removeIf(name -> "小崔".equals(name));
反例二:多线程环境下的 fail-fast
// ❌ 错误:多线程并发修改集合
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 线程1:遍历
new Thread(() -> {
for (String s : list) {
System.out.println(s);
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}).start();
// 线程2:修改
new Thread(() -> {
list.add("D"); // 可能抛出 ConcurrentModificationException
}).start();
纠正:使用 CopyOnWriteArrayList 或 Collections.synchronizedList() 确保线程安全。
面试考点
Q1:Collection 和 Collections 有什么区别?
Collection 是集合框架的接口(java.util.Collection),定义了集合的通用操作;Collections 是工具类(java.util.Collections),提供静态方法操作集合(如 sort、binarySearch 等)。两者的关系类似 接口 vs 工具类,而不是父类子类关系。
Q2:Iterator 和 ListIterator 有什么区别?
① Iterator 可以遍历任何 Collection(List、Set、Queue),ListIterator 仅能遍历 List;② ListIterator 支持双向遍历(hasPrevious/previous),Iterator 仅单向;③ ListIterator 支持在迭代过程中添加元素(add 方法)和修改元素(set 方法),Iterator 仅支持删除;④ ListIterator 可以获取当前位置的索引(nextIndex/previousIndex)。
Q3:fail-fast 和 fail-safe 的区别?
fail-fast 是 JDK 集合的默认行为,迭代时直接操作原集合,对结构性修改敏感,立即抛出 ConcurrentModificationException(如 ArrayList、HashMap);fail-safe 是对集合的快照副本操作,不抛异常但可能读到过期数据(如 CopyOnWriteArrayList、ConcurrentHashMap)。fail-fast 牺牲并发容忍度换取数据一致性,fail-safe 相反。
Q4:为什么 entrySet 遍历 Map 比 keySet + get 更高效?
keySet 遍历时,每次调用
map.get(key)都需要重新计算哈希值并查找(HashMap 中 get 是 O(1) 但仍有常数开销);entrySet 遍历直接返回已配对好的键值对对象,无需二次查找。以 HashMap 为例,entrySet 遍历本质是遍历内部 table 数组 + 链表/红黑树,每条记录只访问一次。
Q5:为什么 Map 不继承 Collection 接口?
这是集合框架设计的经典问题。主要原因是两者语义不兼容:Collection 操作的是单个元素(add(E e)),而 Map 操作的是键值对(put(K, V))。如果 Map 继承 Collection,那么
add()方法应该添加键还是值?此外,contains(Object o)判定的是键还是值也不明确。Java 选择让 Map 独立于 Collection,通过keySet()、values()、entrySet()返回 Collection 视图来桥接两种体系。