# 2026年7月31日 课后作业 — 集合综合运用(含匿名内部类与异常) > **说明**:本次作业为**阶段性综合练习**,覆盖近期核心知识点——**Java 集合框架**(ArrayList / LinkedList / HashMap / TreeMap / HashSet / TreeSet 的新建、增删改查、遍历、迭代器)、**匿名内部类**(Comparator 排序)与**异常处理**(自定义异常 + 集合综合应用),并附 1 道简答题(HashMap 底层原理)。 > > 题目分为 **⭐ 基础(必做)**:作业 1~3;**⭐⭐ 进阶(必做)**:作业 4~5;**简答题(必做)**。请新建 Java 项目,编写并运行代码,将运行结果截图提交。 --- ## 📌 本次作业重要说明 ### 命名要求 | 项目 | 要求 | |------|------| | **包名** | **必须按题目指定的包名创建**(全小写) | | **类名** | 自由命名,但需**见名知意** | | **测试类** | 建议命名为 `TestXxx` 或 `XxxTest` | | **文件名** | 必须与类名一致 | ### 提交目录结构示例 ``` 提交\ └─ 张三\ ├─ p1_list\ ← 第 1 题的包(ArrayList / LinkedList) │ └─ ListDemo.java ├─ p2_map\ ← 第 2 题的包(HashMap / TreeMap) │ └─ MapDemo.java ├─ p3_set\ ← 第 3 题的包(HashSet / TreeSet) │ └─ SetDemo.java ├─ p4_exception\ ← 第 4 题的包(异常 + 集合) │ ├─ InvalidScoreException.java │ └─ ScoreStatisticsDemo.java └─ p5_sort\ ← 第 5 题的包(匿名内部类 + 排序) ├─ Student.java └─ SortDemo.java ``` > ⚠ **注意**: > 1. 包名必须严格按题目要求书写(全小写),目录结构必须与包结构一致 > 2. 每个 Java 文件头部需包含 `@author`、`@title`、`@create` 文档注释 > 3. 简答题(HashMap 底层原理)请写在 Word / TXT 文档中,与代码一起提交 --- # 第一部分:基础题(⭐ 必做) --- ## 作业 1:ArrayList 与 LinkedList 基本操作 — 学生名单管理 **包名**:`p1_list` **知识点**:`ArrayList` / `LinkedList` 创建、增删改查、三种遍历方式(`for+get` / `Iterator` 迭代器 / 增强 for) **场景描述**:使用 ArrayList 和 LinkedList 分别存储学生名单,练习集合的**增删改查**与**三种遍历方式**,体会 List 系集合的通用操作。 **题目要求**: 在 `ListDemo` 类的 `main` 方法中,完成以下操作: ```java import java.util.*; public class ListDemo { public static void main(String[] args) { // 1. 创建 ArrayList 和 LinkedList,各添加 5 个姓名: // "张三", "李四", "王五", "赵六", "孙七" // 2. 输出两个集合的大小(size()):"ArrayList 大小:5 / LinkedList 大小:5" // 3. 查询:获取并输出索引 2 的元素(get(2)):"索引 2 的学生:王五" // 4. 修改:将索引 1 的元素改为"李思"(set()) // 5. 删除:移除索引 3 的元素(remove(index)),输出被删除的元素 // 6. 遍历:分别用【三种方式】遍历 ArrayList: // ① for + size() + get(i) // ② Iterator 迭代器(while + hasNext() + next()) // ③ 增强 for(foreach) // 7. 用 Iterator 迭代器遍历 LinkedList,输出所有元素 } } ``` **预期输出**: ``` ArrayList 大小:5 LinkedList 大小:5 索引 2 的学生:王五 已修改索引 1:李四 → 李思 已删除索引 3:赵六 ========== 方式①:for + get ========== 张三 李思 王五 孙七 ========== 方式②:Iterator 迭代器 ========== 张三 李思 王五 孙七 ========== 方式③:增强 for ========== 张三 李思 王五 孙七 ========== LinkedList 迭代器遍历 ========== 张三 李思 王五 孙七 ``` **思考题**(选做,不要求提交代码): 1. LinkedList 用 `for + get(i)` 遍历为什么效率低?(提示:每次 `get(i)` 从头遍历,O(n²)) 2. `Iterator` 的 `hasNext()` 和 `next()` 分别做什么?指针如何移动? 3. 三种遍历方式各自的适用场景是什么?迭代器和增强 for 是什么关系? --- ## 作业 2:HashMap 与 TreeMap 基本操作 — 学生成绩表 **包名**:`p2_map` **知识点**:`HashMap` / `TreeMap` 创建、`put()` / `get()` / `containsKey()` / `remove()` / `keySet()` / `entrySet()`、Map 两种遍历方式 **场景描述**:用 Map 存储学生成绩(学号 → 成绩)。**HashMap 无序**,**TreeMap 按键自动排序**——对比两者遍历顺序的差异。 **题目要求**: 在 `MapDemo` 类的 `main` 方法中,完成以下操作: ```java import java.util.*; public class MapDemo { public static void main(String[] args) { // 1. 创建 HashMap,添加 4 组成绩: // "x001"->90, "x002"->85, "x003"->92, "x004"->78 // 2. 输出集合大小(size()):"学生人数:4" // 3. 查询:get("x002"):"学号 x002 的成绩:85" // 4. 判断是否包含某键(containsKey("x001")):true // 判断是否包含不存在的键(containsKey("x999")):false // 5. 修改:put("x004", 88)(键已存在 → 覆盖旧值),再次 get 验证 // 6. 删除:remove("x001"),输出删除后的集合大小 // 7. 遍历方式一:keySet() + foreach + get(key),输出所有"学号-成绩" // 输出格式:"学号:x002,成绩:85" // 8. 遍历方式二:entrySet() + foreach + getKey()/getValue(),输出所有"学号-成绩" // 9. 创建 TreeMap,放入同样的 4 组数据 // 用 keySet() 遍历输出,观察:TreeMap 的遍历顺序与 HashMap 有什么不同? // —— 提示:TreeMap 按键(学号)自动排序 } } ``` **预期输出**(HashMap 部分顺序不定,TreeMap 按键排序): ``` 学生人数:4 学号 x002 的成绩:85 包含键 x001:true 包含键 x999:false 修改后 x004 的成绩:88 删除 x001 后,学生人数:3 ========== HashMap 方式一:keySet + get ========== 学号:x002,成绩:85 学号:x003,成绩:92 学号:x004,成绩:88 ========== HashMap 方式二:entrySet ========== 学号:x002,成绩:85 学号:x003,成绩:92 学号:x004,成绩:88 ========== TreeMap keySet 遍历(按键排序) ========== 学号:x002,成绩:85 学号:x003,成绩:92 学号:x004,成绩:88 ``` **思考题**(选做,不要求提交代码): 1. HashMap 和 TreeMap 的遍历顺序有什么不同?为什么?(提示:哈希定位 vs 红黑树中序排序) 2. `keySet()` 遍历和 `entrySet()` 遍历的效率差异?为什么? 3. 为什么 `keySet()` 返回的是 Set 而不是 List?(提示:键不可重复) --- ## 作业 3:HashSet 与 TreeSet 基本操作 — 单词去重与排序 **包名**:`p3_set` **知识点**:`HashSet` 去重、`TreeSet` 自动排序、迭代器 / 增强 for 遍历 **场景描述**:Set 集合**无序、不可重复**。HashSet 去重(无序),TreeSet 自动排序(有序)。模拟水果清单去重与数字排序。 **题目要求**: 在 `SetDemo` 类的 `main` 方法中,完成以下操作: ```java import java.util.*; public class SetDemo { public static void main(String[] args) { // 1. 创建 HashSet,依次添加:香蕉, 苹果, 葡萄, 苹果, 西瓜, 香蕉 // 观察输出:重复元素"苹果""香蕉"只保留一个(自动去重) // 2. 输出集合大小:应输出 4(6 个元素去重后剩 4 个) // 3. 用 Iterator 迭代器遍历输出所有元素 // 4. 创建 TreeSet,添加同样的水果 // 观察输出:自动按【字典序】排序 // 5. 创建 TreeSet,添加:50, 20, 80, 10, 60 // 观察输出:自动按【数值升序】排序 // 6. 用增强 for 遍历 TreeSet,输出所有元素 } } ``` **预期输出**(HashSet 顺序不定): ``` ========== HashSet 去重 ========== 集合大小:4 迭代器遍历:西瓜 香蕉 苹果 葡萄 ========== TreeSet 字典序排序 ========== 苹果 葡萄 西瓜 香蕉 ========== TreeSet 数值升序 ========== 10 20 50 60 80 ``` **思考题**(选做,不要求提交代码): 1. HashSet 和 TreeSet 的区别是什么?(提示:哈希去重 vs 红黑树排序) 2. Set 为什么没有 `get(index)`?如何遍历 Set?(提示:无序无索引,用迭代器/增强 for) 3. 自定义对象放进 TreeSet 会怎样?需要满足什么条件?(提示:实现 Comparable 或传入 Comparator) --- # 第二部分:进阶题(⭐⭐ 必做) --- ## 作业 4:异常处理 + 集合综合 — 学生成绩统计系统 **包名**:`p4_exception` **知识点**:`try-catch` 多 catch 块、自定义异常、异常处理 + ArrayList 综合应用、迭代器遍历 **场景描述**:编写成绩统计程序,把输入的成绩存入 `ArrayList`,计算总分和平均分。要求处理三类异常:**输入不是整数**(`InputMismatchException`)、**成绩不合法**(自定义异常 `InvalidScoreException`)、**索引越界**(`IndexOutOfBoundsException`)。 **题目要求**: 先定义自定义异常类: ```java // InvalidScoreException.java public class InvalidScoreException extends RuntimeException { public InvalidScoreException(String message) { super(message); } } ``` 再编写 `ScoreStatisticsDemo`: ```java import java.util.ArrayList; import java.util.InputMismatchException; import java.util.Scanner; public class ScoreStatisticsDemo { public static void main(String[] args) { Scanner sc = new Scanner(System.in); ArrayList scores = new ArrayList<>(); // 1. 循环输入 5 个成绩(0~100),存入 scores // 提示:for (int i = 0; i < 5; i++) { int s = sc.nextInt(); scores.add(s); } // 2. 使用 try-catch 处理以下异常: // a. 输入不是整数 → InputMismatchException,提示"请输入整数"并要求重新输入 // b. 成绩不在 0~100 → 抛出 InvalidScoreException(自定义异常) // if (s < 0 || s > 100) { throw new InvalidScoreException("成绩必须在0~100之间"); } // c. 下标越界 → IndexOutOfBoundsException // 3. 计算总分和平均分(用 for 或迭代器遍历求和) // 4. 使用迭代器遍历输出所有成绩 } } ``` **运行效果示意**: ``` 请输入第 1 个成绩(0~100):abc 输入错误,请输入整数! 请输入第 1 个成绩(0~100):85 请输入第 2 个成绩(0~100):92 请输入第 3 个成绩(0~100):120 成绩不合法:成绩必须在0~100之间 请输入第 3 个成绩(0~100):78 请输入第 4 个成绩(0~100):66 请输入第 5 个成绩(0~100):90 ========== 成绩统计结果 ========== 成绩列表:85 92 78 66 90 总分:411,平均分:82.2 ``` **提示**: - `InputMismatchException` 发生后需要调用 `sc.nextLine()` 清空输入缓冲,否则会死循环 - 多 catch 块中,**子类异常在前,父类异常在后**(如 `InputMismatchException` 在 `Exception` 之前) - 可以结合 `while` 循环实现"输入错误就重试"的鲁棒性 **思考题**(选做,不要求提交代码): 1. 多 catch 块的书写顺序有什么要求?为什么子类必须在前? 2. 自定义异常继承 `Exception`(编译期)和 `RuntimeException`(运行期)有什么区别? 3. 为什么输入不是整数后要调用 `nextLine()` 清空缓冲? --- ## 作业 5:匿名内部类 + 集合排序 — 学生排序系统 **包名**:`p5_sort` **知识点**:**匿名内部类**实现 `Comparator`、`Collections.sort()`、`TreeSet` 自定义排序、List / Set 排序 **场景描述**:定义 `Student` 类(name + age + score),用**匿名内部类**实现 `Comparator` 接口,对集合中的学生按年龄、成绩、姓名排序。体会匿名内部类"临时定义一个实现类"的便捷性。 **题目要求**: 先定义 `Student` 类: ```java // Student.java public class Student { private String name; private int age; private double score; public Student(String name, int age, double score) { this.name = name; this.age = age; this.score = score; } // TODO: 补全 getter / setter 方法 @Override public String toString() { return "Student{name='" + name + "', age=" + age + ", score=" + score + "}"; } } ``` 再编写 `SortDemo`: ```java import java.util.*; public class SortDemo { public static void main(String[] args) { // 1. 创建 ArrayList,添加 4 名学生: // 张三 20 85 / 李四 22 90 / 王五 19 78 / 赵六 21 88 // 2. 使用【匿名内部类】实现 Comparator,按年龄升序排序 // Collections.sort(list, new Comparator() { // @Override // public int compare(Student s1, Student s2) { // return s1.getAge() - s2.getAge(); // 年龄升序 // } // }); // 3. 输出按年龄排序后的结果 // 4. 再写一个匿名内部类 Comparator,按成绩(score)降序排序,输出结果 // 提示:成绩是 double,比较可用 Double.compare(s2.getScore(), s1.getScore()) // 5. 创建一个 TreeSet,传入按姓名(name)排序的匿名 Comparator // 把 4 名学生放入 TreeSet,用迭代器遍历输出,观察自动排序效果 } } ``` **预期输出**: ``` ========== 按年龄升序排序 ========== Student{name='王五', age=19, score=78.0} Student{name='张三', age=20, score=85.0} Student{name='赵六', age=21, score=88.0} Student{name='李四', age=22, score=90.0} ========== 按成绩降序排序 ========== Student{name='李四', age=22, score=90.0} Student{name='赵六', age=21, score=88.0} Student{name='张三', age=20, score=85.0} Student{name='王五', age=19, score=78.0} ========== TreeSet 按姓名排序 ========== Student{name='张三', age=20, score=85.0} Student{name='李四', age=22, score=90.0} Student{name='王五', age=19, score=78.0} Student{name='赵六', age=21, score=88.0} ``` **思考题**(选做,不要求提交代码): 1. 匿名内部类的语法格式是什么?这里为什么可以用匿名内部类?(提示:Comparator 是接口) 2. `Comparator` 的 `compare()` 返回负数 / 0 / 正数分别代表什么? 3. `Comparable` 和 `Comparator` 的区别是什么?(提示:类自身排序 vs 外部排序器) --- # 第三部分:简答题(必做) ## 简答题:说明 HashMap 的底层原理 请用文字说明(可配合画图)回答以下问题: 1. **HashMap 的数据结构是什么?** JDK 1.7 和 JDK 1.8 有什么区别? 2. **HashMap 是如何保证键 Key 的唯一性的?** 依赖哪两个方法? 3. **什么是哈希冲突?** HashMap 是如何解决的? 4. **HashMap 的树化条件和退化条件分别是什么?**(提示:`TREEIFY_THRESHOLD`、`MIN_TREEIFY_CAPACITY`、`UNTREEIFY_THRESHOLD`) 5. **HashMap 的默认容量和负载因子是多少?** 负载因子的作用是什么? **答题要点提示**: - 数据结构:**数组 + 链表**(JDK1.7);**数组 + 链表 / 数组 + 红黑树**(JDK1.8) - 键唯一性:先 `hashCode()` 定位,再 `equals()` 比较 - 哈希冲突:多个键的哈希值相同 → 挂到同一位置的链表上(链地址法) - 树化条件:**链表节点长度 > 8 且 数组长度 > 64**(两个条件同时满足);退化条件:**节点长度 < 6** - 默认容量 **16**,负载因子 **0.75f**:已存元素超过容量 × 0.75 时触发扩容