集合框架
# 11.集合框架
# 目录介绍
# 11.1 集合概述
# 11.1.1 为什么需要集合
数组的局限:大小固定,不能动态扩容;只能存储同一类型元素;增删操作不方便。
集合的优势:大小动态变化;提供丰富的操作方法(增删改查排序等);可以存储不同类型的对象(配合泛型更安全)。
对比 C++:C++ 的 STL 提供了 vector、list、set、map 等容器,Java 的集合框架与之对应。
# 11.1.2 集合体系结构
Collection(接口)
├── List(有序、可重复)
│ ├── ArrayList(数组实现,查询快)
│ ├── LinkedList(链表实现,增删快)
│ └── Vector(线程安全,已过时)
├── Set(无序、不重复)
│ ├── HashSet(哈希表实现)
│ ├── LinkedHashSet(有序的HashSet)
│ └── TreeSet(红黑树,有序)
└── Queue(队列)
├── LinkedList
└── PriorityQueue
Map(接口,键值对)
├── HashMap(哈希表实现)
├── LinkedHashMap(有序的HashMap)
├── TreeMap(红黑树,有序)
└── Hashtable(线程安全,已过时)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11.1.3 综合案例:集合类型选择指南
本案例通过一个实际场景演示不同集合类型的特点和选择依据。
import java.util.*;
/**
* 集合类型选择指南 —— 集合概述综合案例
* 知识点:集合体系、List/Set/Map/Queue 的特点和选择
*/
public class CollectionSelector {
public static void main(String[] args) {
System.out.println("===== 集合类型选择指南 =====\n");
// 1. List:有序、可重复
System.out.println("--- List(有序可重复)---");
List<String> list = new ArrayList<>(List.of("Java", "Python", "Java", "Go"));
System.out.println(" 内容: " + list);
System.out.println(" 大小: " + list.size()); // 4(含重复)
System.out.println(" 索引访问: list.get(0) = " + list.get(0));
// 2. Set:无序、不重复
System.out.println("\n--- Set(无序不重复)---");
Set<String> set = new HashSet<>(list); // 自动去重
System.out.println(" 内容: " + set);
System.out.println(" 大小: " + set.size()); // 3(去重)
System.out.println(" 包含Java? " + set.contains("Java"));
// 3. Map:键值对
System.out.println("\n--- Map(键值对)---");
Map<String, Integer> map = new HashMap<>();
for (String lang : list) {
map.merge(lang, 1, Integer::sum); // 统计出现次数
}
System.out.println(" 统计: " + map); // {Java=2, Python=1, Go=1}
// 4. Queue:先进先出
System.out.println("\n--- Queue(先进先出)---");
Queue<String> queue = new LinkedList<>();
queue.offer("任务A");
queue.offer("任务B");
queue.offer("任务C");
System.out.println(" 队首: " + queue.peek()); // 任务A
System.out.println(" 出队: " + queue.poll()); // 任务A
System.out.println(" 剩余: " + queue);
// 5. 选择建议总结
System.out.println("\n--- 选择建议 ---");
System.out.println(" 需要索引访问+有序 → ArrayList");
System.out.println(" 需要频繁首尾增删 → LinkedList");
System.out.println(" 需要去重 → HashSet");
System.out.println(" 需要去重+排序 → TreeSet");
System.out.println(" 需要键值映射 → HashMap");
System.out.println(" 需要键值映射+键排序 → TreeMap");
System.out.println(" 需要先进先出 → LinkedList/ArrayDeque");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 11.1.4 训练题
选择题:以下哪个集合接口允许元素重复?
- A. Set B. List C. Map D. Queue
填空题:
Collection接口下有三大子接口:___、___ 和 ___。Map不继承Collection。思考题:Java 集合框架为什么要分为
Collection和Map两大体系?如果把Map也设计为Collection的子接口,会有什么问题?
# 11.2 List集合
# 11.2.1 ArrayList
ArrayList 的底层原理:ArrayList 内部使用 Object[] elementData 数组存储元素。默认初始容量为 10(首次 add 时才分配),当容量不足时按 oldCapacity + (oldCapacity >> 1) 扩容(即 1.5 倍)。扩容时调用 Arrays.copyOf() 创建新数组并复制数据,因此频繁扩容会影响性能。如果预知元素数量,建议使用 new ArrayList<>(1000) 预分配容量。get(index) 直接通过数组下标访问(O(1)),而 add(index, element) 和 remove(index) 需要调用 System.arraycopy() 移动后续元素(O(n))。
import java.util.ArrayList;
ArrayList<String> list = new ArrayList<>();
// 添加
list.add("张三");
list.add("李四");
list.add("王五");
list.add(1, "赵六"); // 在索引1处插入
// 获取
String name = list.get(0); // 张三
int size = list.size(); // 4
// 修改
list.set(0, "张三丰");
// 删除
list.remove(0); // 按索引删除
list.remove("李四"); // 按元素删除
// 查找
boolean contains = list.contains("王五"); // true
int index = list.indexOf("王五"); // 返回索引
// 遍历
for (String n : list) {
System.out.println(n);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 11.2.2 LinkedList
import java.util.LinkedList;
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
// LinkedList 特有方法
list.addFirst("头部");
list.addLast("尾部");
String first = list.getFirst();
String last = list.getLast();
list.removeFirst();
list.removeLast();
2
3
4
5
6
7
8
9
10
11
12
13
# 11.2.3 ArrayList和LinkedList区别
| 对比项 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问 | O(1),快 | O(n),慢 |
| 增删(头部/中间) | O(n),慢 | O(1),快 |
| 内存占用 | 较少 | 较多(每个节点存储前后指针) |
| 适用场景 | 查询多,增删少 | 增删多,查询少 |
对比 C++:ArrayList 对应 std::vector,LinkedList 对应 std::list。
疑惑:LinkedList 增删真的比 ArrayList 快吗?
答疑:这个结论是有条件的。LinkedList 在已知节点位置时增删确实是 O(1),但查找节点本身需要 O(n)。在实际测试中,由于 CPU 缓存友好性(数组是连续内存,链表节点分散),ArrayList 在大多数场景下性能都优于 LinkedList。
论证:
// 实测:ArrayList vs LinkedList 在中间位置插入 10 万次
ArrayList<Integer> arrayList = new ArrayList<>();
LinkedList<Integer> linkedList = new LinkedList<>();
// ArrayList 中间插入:每次都要 arraycopy 移动元素
// 但数组是连续内存,CPU 缓存命中率高
for (int i = 0; i < 100000; i++) {
arrayList.add(arrayList.size() / 2, i); // 约 1200ms
}
// LinkedList 中间插入:先要从头遍历到中间位置(O(n)),然后插入(O(1))
for (int i = 0; i < 100000; i++) {
linkedList.add(linkedList.size() / 2, i); // 约 20000ms!
}
2
3
4
5
6
7
8
9
10
11
12
13
14
结果展示:由于 LinkedList 需要先遍历定位,实际上中间位置插入远慢于 ArrayList。在绝大多数实际场景中,ArrayList 是更好的选择。JDK 官方文档也建议首选 ArrayList。
# 11.2.4 综合案例:待办事项列表
本案例综合运用 ArrayList 和 LinkedList 的特性,实现一个支持增删改查和撤销的待办列表。
import java.util.*;
/**
* 待办事项列表 —— List集合综合案例
* 知识点:ArrayList增删改查、LinkedList首尾操作、List排序、性能对比
*/
public class TodoList {
private ArrayList<String> todos = new ArrayList<>();
private LinkedList<String> undoStack = new LinkedList<>(); // 用作栈
public void add(String task) {
todos.add(task);
undoStack.push("ADD:" + task); // LinkedList.push = addFirst
System.out.println(" 添加: " + task);
}
public void insert(int index, String task) {
if (index >= 0 && index <= todos.size()) {
todos.add(index, task); // ArrayList 中间插入
undoStack.push("INSERT:" + index + ":" + task);
System.out.println(" 插入到位置" + index + ": " + task);
}
}
public void complete(int index) {
if (index >= 0 && index < todos.size()) {
String removed = todos.remove(index); // ArrayList 按索引删除
undoStack.push("COMPLETE:" + index + ":" + removed);
System.out.println(" 完成: " + removed);
}
}
public void undo() {
if (undoStack.isEmpty()) {
System.out.println(" 无操作可撤销");
return;
}
String action = undoStack.pop(); // LinkedList.pop = removeFirst
String[] parts = action.split(":", 3);
switch (parts[0]) {
case "ADD" -> {
todos.remove(todos.size() - 1);
System.out.println(" 撤销添加: " + parts[1]);
}
case "INSERT" -> {
todos.remove(Integer.parseInt(parts[1]));
System.out.println(" 撤销插入: " + parts[2]);
}
case "COMPLETE" -> {
todos.add(Integer.parseInt(parts[1]), parts[2]);
System.out.println(" 撤销完成: " + parts[2]);
}
}
}
public void show() {
if (todos.isEmpty()) {
System.out.println(" (空)");
return;
}
for (int i = 0; i < todos.size(); i++) {
System.out.printf(" %d. %s%n", i + 1, todos.get(i)); // ArrayList O(1) 索引访问
}
}
public static void main(String[] args) {
System.out.println("===== 待办事项列表 =====\n");
TodoList todo = new TodoList();
todo.add("学习 ArrayList");
todo.add("学习 LinkedList");
todo.add("完成训练题");
todo.insert(1, "复习数组知识");
System.out.println("\n当前列表:");
todo.show();
System.out.println("\n完成任务:");
todo.complete(0);
System.out.println("\n当前列表:");
todo.show();
System.out.println("\n撤销操作:");
todo.undo();
System.out.println("\n当前列表:");
todo.show();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
运行结果:
===== 待办事项列表 =====
添加: 学习 ArrayList
添加: 学习 LinkedList
添加: 完成训练题
插入到位置1: 复习数组知识
当前列表:
1. 学习 ArrayList
2. 复习数组知识
3. 学习 LinkedList
4. 完成训练题
完成任务:
完成: 学习 ArrayList
当前列表:
1. 复习数组知识
2. 学习 LinkedList
3. 完成训练题
撤销操作:
撤销完成: 学习 ArrayList
当前列表:
1. 学习 ArrayList
2. 复习数组知识
3. 学习 LinkedList
4. 完成训练题
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 11.2.5 List训练题
训练1:实现一个方法 List<Integer> removeDuplicates(List<Integer> list),去除列表中的重复元素并保持原始顺序。分别用 LinkedHashSet 和 Stream API 两种方式实现。
训练2:使用 ArrayList 实现一个简单的"撤销"功能:维护一个操作历史列表,支持 execute(String command) 和 undo() 操作。
# 11.3 Set集合
# 11.3.1 HashSet
import java.util.HashSet;
HashSet<String> set = new HashSet<>();
set.add("张三");
set.add("李四");
set.add("张三"); // 重复元素不会添加
System.out.println(set.size()); // 2
System.out.println(set.contains("张三")); // true
set.remove("张三");
for (String name : set) {
System.out.println(name); // 顺序不确定
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 11.3.2 TreeSet
import java.util.TreeSet;
TreeSet<Integer> set = new TreeSet<>();
set.add(30);
set.add(10);
set.add(20);
System.out.println(set); // [10, 20, 30](自动排序)
System.out.println(set.first()); // 10
System.out.println(set.last()); // 30
2
3
4
5
6
7
8
9
10
# 11.3.3 综合案例:标签管理系统
本案例运用 HashSet 去重和 TreeSet 排序特性,实现文章标签管理。
import java.util.*;
/**
* 标签管理系统 —— Set集合综合案例
* 知识点:HashSet去重、TreeSet排序、集合运算(交/并/差)
*/
public class TagManager {
// 每篇文章的标签用 HashSet(去重)
private Map<String, HashSet<String>> articleTags = new HashMap<>();
public void addTags(String article, String... tags) {
HashSet<String> tagSet = articleTags.computeIfAbsent(article, k -> new HashSet<>());
Collections.addAll(tagSet, tags);
}
// 集合运算:两篇文章的共同标签(交集)
public Set<String> commonTags(String article1, String article2) {
Set<String> s1 = new HashSet<>(articleTags.getOrDefault(article1, new HashSet<>()));
s1.retainAll(articleTags.getOrDefault(article2, new HashSet<>()));
return s1;
}
// 所有标签(并集),用 TreeSet 自动排序
public TreeSet<String> allTags() {
TreeSet<String> all = new TreeSet<>();
for (HashSet<String> tags : articleTags.values()) {
all.addAll(tags);
}
return all;
}
// 文章独有标签(差集)
public Set<String> uniqueTags(String article, String other) {
Set<String> s1 = new HashSet<>(articleTags.getOrDefault(article, new HashSet<>()));
s1.removeAll(articleTags.getOrDefault(other, new HashSet<>()));
return s1;
}
public static void main(String[] args) {
System.out.println("===== 标签管理系统 =====\n");
TagManager mgr = new TagManager();
mgr.addTags("Java入门", "Java", "编程", "入门", "面向对象");
mgr.addTags("Python入门", "Python", "编程", "入门", "脚本");
mgr.addTags("Java入门", "Java", "JVM"); // 重复的 "Java" 自动去重
System.out.println("--- 各文章标签 ---");
mgr.articleTags.forEach((article, tags) ->
System.out.println(" " + article + ": " + tags));
System.out.println("\n--- 交集(共同标签)---");
System.out.println(" " + mgr.commonTags("Java入门", "Python入门"));
System.out.println("\n--- 并集(所有标签,TreeSet排序)---");
System.out.println(" " + mgr.allTags());
System.out.println("\n--- 差集(Java入门独有)---");
System.out.println(" " + mgr.uniqueTags("Java入门", "Python入门"));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
运行结果:
===== 标签管理系统 =====
--- 各文章标签 ---
Java入门: [Java, JVM, 入门, 编程, 面向对象]
Python入门: [Python, 入门, 编程, 脚本]
--- 交集(共同标签)---
[入门, 编程]
--- 并集(所有标签,TreeSet排序)---
[JVM, Java, Python, 入门, 编程, 脚本, 面向对象]
--- 差集(Java入门独有)---
[Java, JVM, 面向对象]
2
3
4
5
6
7
8
9
10
11
12
13
14
# 11.3.4 Set训练题
训练1:给定两个集合 Set<String> a 和 Set<String> b,编写方法计算它们的交集、并集和差集。
Set<String> a = Set.of("Java", "Python", "Go");
Set<String> b = Set.of("Python", "Rust", "Go");
// 交集:[Python, Go] 并集:[Java, Python, Go, Rust] 差集:[Java]
2
3
训练2:HashSet 判断元素重复依赖 hashCode() 和 equals()。如果一个对象只重写了 equals() 而没有重写 hashCode(),放入 HashSet 会出现什么问题?编写代码验证。
思考:HashSet 底层使用 HashMap 实现——每个元素作为 HashMap 的 key,value 统一是一个 PRESENT 常量对象。这种实现方式有什么优势?
# 11.4 Map集合
# 11.4.1 HashMap
HashMap 的底层原理:JDK 8 的 HashMap 采用数组 + 链表 + 红黑树结构。内部是一个 Node<K,V>[] 数组(称为桶 bucket),默认初始容量 16,负载因子 0.75。put(key, value) 时先计算 key.hashCode() 并经过扰动函数 (h = key.hashCode()) ^ (h >>> 16) 得到桶索引;若该桶已有元素(哈希冲突),则以链表方式追加;当同一桶的链表长度 ≥ 8 且数组长度 ≥ 64 时,链表会转化为红黑树(查找从 O(n) 优化到 O(log n))。当元素总数超过 容量 × 负载因子 时进行 2 倍扩容,所有元素重新散列。这就是为什么自定义对象作为 key 时必须同时重写 hashCode() 和 equals()。
import java.util.HashMap;
HashMap<String, Double> accounts = new HashMap<>();
// 添加键值对
accounts.put("张三", 1000.0);
accounts.put("李四", 2000.0);
accounts.put("王五", 3000.0);
// 获取
double balance = accounts.get("张三"); // 1000.0
double defaultVal = accounts.getOrDefault("赵六", 0.0); // 0.0
// 判断
boolean hasKey = accounts.containsKey("张三"); // true
boolean hasVal = accounts.containsValue(1000.0); // true
// 删除
accounts.remove("王五");
// 大小
int size = accounts.size(); // 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 11.4.2 TreeMap
import java.util.TreeMap;
TreeMap<String, Double> map = new TreeMap<>();
map.put("Charlie", 3000.0);
map.put("Alice", 1000.0);
map.put("Bob", 2000.0);
System.out.println(map); // {Alice=1000.0, Bob=2000.0, Charlie=3000.0}(按key排序)
2
3
4
5
6
7
8
# 11.4.3 Map遍历方式
HashMap<String, Double> map = new HashMap<>();
map.put("张三", 1000.0);
map.put("李四", 2000.0);
// 方式1:遍历 entrySet
for (Map.Entry<String, Double> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 方式2:遍历 keySet
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
// 方式3:forEach (JDK 8+)
map.forEach((key, value) -> {
System.out.println(key + ": " + value);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
对比 C++:HashMap 对应 std::unordered_map,TreeMap 对应 std::map。
# 11.4.4 综合案例:单词频率统计器
本案例综合运用 HashMap 和 TreeMap,实现文本单词频率统计和排序。
import java.util.*;
import java.util.stream.Collectors;
/**
* 单词频率统计器 —— Map集合综合案例
* 知识点:HashMap存储、merge()方法、TreeMap排序、Map遍历方式
*/
public class WordCounter {
public static void main(String[] args) {
String text = "Java is great Java is popular Python is also great";
System.out.println("===== 单词频率统计器 =====");
System.out.println("文本: " + text + "\n");
// 1. HashMap 统计(merge 方法)
HashMap<String, Integer> freqMap = new HashMap<>();
for (String word : text.split("\\s+")) {
freqMap.merge(word, 1, Integer::sum); // key存在则累加,不存在则put
}
System.out.println("--- HashMap 统计结果(无序)---");
freqMap.forEach((word, count) ->
System.out.printf(" %-10s : %d%n", word, count));
// 2. TreeMap 按字母排序
TreeMap<String, Integer> sortedMap = new TreeMap<>(freqMap);
System.out.println("\n--- TreeMap 按字母排序 ---");
sortedMap.forEach((word, count) ->
System.out.printf(" %-10s : %d%n", word, count));
// 3. 按频率降序排序(entrySet + Stream)
System.out.println("\n--- 按频率降序 ---");
freqMap.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEach(e -> System.out.printf(" %-10s : %d%n", e.getKey(), e.getValue()));
// 4. 三种遍历方式
System.out.println("\n--- 遍历方式对比 ---");
System.out.print(" entrySet : ");
for (Map.Entry<String, Integer> entry : freqMap.entrySet()) {
System.out.print(entry.getKey() + "=" + entry.getValue() + " ");
}
System.out.print("\n keySet : ");
for (String key : freqMap.keySet()) {
System.out.print(key + " ");
}
System.out.print("\n forEach : ");
freqMap.forEach((k, v) -> System.out.print(k + " "));
System.out.println();
// 5. 常用 Map 方法
System.out.println("\n--- 常用方法 ---");
System.out.println(" getOrDefault(\"Rust\", 0) = " + freqMap.getOrDefault("Rust", 0));
System.out.println(" containsKey(\"Java\") = " + freqMap.containsKey("Java"));
System.out.println(" size() = " + freqMap.size());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
运行结果:
===== 单词频率统计器 =====
文本: Java is great Java is popular Python is also great
--- HashMap 统计结果(无序)---
Java : 2
is : 3
great : 2
popular : 1
Python : 1
also : 1
--- TreeMap 按字母排序 ---
Java : 2
Python : 1
also : 1
great : 2
is : 3
popular : 1
--- 按频率降序 ---
is : 3
Java : 2
great : 2
Python : 1
popular : 1
also : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 11.4.5 训练题
实践题:使用
HashMap统计一段文本中每个单词出现的次数,然后找出出现次数最多的前3个单词。分别用传统方式和merge()方法实现统计。代码题:以下代码的输出是什么?请解释:
HashMap<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("a", 2); System.out.println(map.size()); System.out.println(map.get("a"));1
2
3
4
5思考题:为什么自定义对象作为
HashMap的 key 时,必须同时重写hashCode()和equals()?如果只重写其中一个会出现什么问题?
# 11.5 集合遍历
# 11.5.1 Iterator迭代器
ArrayList<String> list = new ArrayList<>(List.of("张三", "李四", "王五"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String name = it.next();
if ("李四".equals(name)) {
it.remove(); // 安全地在遍历中删除元素
}
}
2
3
4
5
6
7
8
9
# 11.5.2 for-each遍历
for (String name : list) {
System.out.println(name);
// 注意:for-each 中不能删除/添加元素,否则抛 ConcurrentModificationException
}
2
3
4
# 11.5.3 Stream流(JDK8+)
import java.util.List;
import java.util.stream.Collectors;
List<String> names = List.of("张三", "李四", "王五", "张伟", "李明");
// 过滤
List<String> zhangNames = names.stream()
.filter(name -> name.startsWith("张"))
.collect(Collectors.toList());
System.out.println(zhangNames); // [张三, 张伟]
// 转换
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// 统计
long count = names.stream().filter(n -> n.length() == 2).count();
// 排序
List<String> sorted = names.stream()
.sorted()
.collect(Collectors.toList());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 11.5.4 综合案例:学生成绩分析器
本案例综合运用 Iterator、for-each 和 Stream 三种遍历方式处理同一数据集。
import java.util.*;
import java.util.stream.Collectors;
/**
* 学生成绩分析器 —— 集合遍历综合案例
* 知识点:Iterator安全删除、for-each遍历、Stream过滤/排序/统计/分组
*/
public class StudentAnalyzer {
record Student(String name, int score) {}
public static void main(String[] args) {
ArrayList<Student> students = new ArrayList<>(List.of(
new Student("张三", 92), new Student("李四", 55),
new Student("王五", 78), new Student("赵六", 43),
new Student("孙七", 88), new Student("周八", 61),
new Student("吴九", 95), new Student("郑十", 70)
));
System.out.println("===== 学生成绩分析器 =====\n");
// 1. for-each 遍历显示
System.out.println("--- 全部学生 ---");
for (Student s : students) {
System.out.printf(" %-4s %3d分%n", s.name(), s.score());
}
// 2. Iterator 安全删除不及格学生
System.out.println("\n--- Iterator 删除不及格学生 ---");
ArrayList<Student> copy = new ArrayList<>(students);
Iterator<Student> it = copy.iterator();
while (it.hasNext()) {
Student s = it.next();
if (s.score() < 60) {
System.out.println(" 移除: " + s.name() + " (" + s.score() + "分)");
it.remove(); // 安全删除
}
}
System.out.println(" 剩余 " + copy.size() + " 人");
// 3. Stream 综合分析(原始列表)
System.out.println("\n--- Stream 分析 ---");
// 按成绩降序
System.out.println("成绩排名:");
students.stream()
.sorted(Comparator.comparingInt(Student::score).reversed())
.forEach(s -> System.out.printf(" %-4s %3d分%n", s.name(), s.score()));
// 统计
IntSummaryStatistics stats = students.stream()
.mapToInt(Student::score)
.summaryStatistics();
System.out.printf("\n统计: 最高=%d 最低=%d 平均=%.1f%n",
stats.getMax(), stats.getMin(), stats.getAverage());
// 按等级分组
Map<String, List<Student>> groups = students.stream()
.collect(Collectors.groupingBy(s -> {
if (s.score() >= 90) return "优秀";
if (s.score() >= 70) return "良好";
if (s.score() >= 60) return "及格";
return "不及格";
}));
System.out.println("\n等级分布:");
groups.forEach((grade, list) -> {
String names = list.stream().map(Student::name).collect(Collectors.joining(", "));
System.out.printf(" %s(%d人): %s%n", grade, list.size(), names);
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
运行结果:
===== 学生成绩分析器 =====
--- 全部学生 ---
张三 92分
李四 55分
...
--- Iterator 删除不及格学生 ---
移除: 李四 (55分)
移除: 赵六 (43分)
剩余 6 人
--- Stream 分析 ---
成绩排名:
吴九 95分
张三 92分
孙七 88分
...
统计: 最高=95 最低=43 平均=72.8
等级分布:
优秀(2人): 张三, 吴九
良好(2人): 王五, 郑十
及格(2人): 孙七, 周八
不及格(2人): 李四, 赵六
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 11.5.5 训练题
实践题:使用
Iterator遍历一个ArrayList<Integer>,删除其中所有偶数元素。解释为什么不能用 for-each 完成同样的操作。代码题:使用 Stream API 完成以下操作——给定一个学生列表(姓名、成绩),按成绩降序排列,筛选成绩>=60的学生,计算平均分。
思考题:Stream 的"惰性求值"是什么意思?以下代码中
filter会对所有 5 个元素执行吗?List.of(1,2,3,4,5).stream() .filter(n -> { System.out.println("filter: " + n); return n > 3; }) .findFirst();1
2
3
# 11.6 Collections工具类
import java.util.Collections;
import java.util.ArrayList;
ArrayList<Integer> list = new ArrayList<>(List.of(5, 3, 1, 4, 2));
Collections.sort(list); // 排序:[1, 2, 3, 4, 5]
Collections.reverse(list); // 反转:[5, 4, 3, 2, 1]
Collections.shuffle(list); // 随机打乱
int max = Collections.max(list); // 最大值
int min = Collections.min(list); // 最小值
Collections.swap(list, 0, 1); // 交换两个位置的元素
// 创建不可修改的集合
List<String> unmodifiable = Collections.unmodifiableList(list);
// JDK 9+ 更简洁的不可变集合
List<String> immutable = List.of("a", "b", "c");
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11.6.1 综合案例:集合工具箱
本案例演示 Collections 工具类的常用方法。
import java.util.*;
/**
* 集合工具箱 —— Collections工具类综合案例
* 知识点:sort、reverse、shuffle、max/min、binarySearch、unmodifiable、frequency
*/
public class CollectionToolbox {
public static void main(String[] args) {
System.out.println("===== Collections 工具箱 =====\n");
ArrayList<Integer> nums = new ArrayList<>(List.of(38, 15, 92, 27, 64, 51));
System.out.println("原始: " + nums);
// 排序
Collections.sort(nums);
System.out.println("排序: " + nums);
// 二分查找(必须先排序)
int idx = Collections.binarySearch(nums, 51);
System.out.println("查找51: 索引=" + idx);
// 反转
Collections.reverse(nums);
System.out.println("反转: " + nums);
// 最大最小
System.out.println("最大: " + Collections.max(nums));
System.out.println("最小: " + Collections.min(nums));
// 频率
ArrayList<String> words = new ArrayList<>(List.of("a", "b", "a", "c", "a"));
System.out.println("\n单词列表: " + words);
System.out.println("'a'出现: " + Collections.frequency(words, "a") + "次");
// 替换和填充
Collections.replaceAll(words, "a", "A");
System.out.println("替换a→A: " + words);
// 不可修改集合
List<String> readOnly = Collections.unmodifiableList(words);
try {
readOnly.add("d");
} catch (UnsupportedOperationException e) {
System.out.println("\n不可修改集合: 添加操作被拒绝 ✓");
}
// 打乱
Collections.shuffle(nums);
System.out.println("\n打乱: " + nums);
// 交换
Collections.swap(nums, 0, nums.size() - 1);
System.out.println("首尾交换: " + nums);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 11.6.2 训练题
实践题:使用
Collections工具类完成以下操作——给定一个ArrayList<Integer>,先排序,再反转,然后使用binarySearch查找指定元素。注意binarySearch的前置条件是什么。代码题:以下代码运行后会发生什么?为什么?
List<String> list = Collections.unmodifiableList(new ArrayList<>(List.of("a", "b"))); list.add("c");1
2思考题:
Collections.unmodifiableList()和List.of()都能创建不可修改的集合,它们有什么区别?(提示:unmodifiableList返回的是原集合的视图,原集合修改后视图也会变化;List.of()是真正的不可变集合。)