# 2026-07-31 Iterator 迭代器与 Map 集合专项练习(课堂练习用) > **说明**:以下习题围绕今日授课核心内容——**Iterator 迭代器** 与 **Map 集合(双列集合)** 展开,涵盖 for 循环与迭代器遍历对比、Map 键值对基本操作、Map 两种遍历方式(keySet+get / entrySet)、自定义类型作为 HashMap 的 Key(重写 hashCode/equals)、HashMap 底层原理应用等知识点。 > > 题目分为 **基础练习**(迭代器与 Map 基本操作,3 题)和 **进阶挑战**(Map 综合应用,2 题)。 --- # 第一部分:基础练习 > 以下练习围绕今日授课核心内容展开,由浅入深。 --- ## 练习 1:Iterator 迭代器遍历(for 循环 vs 迭代器) **难度**:⭐ **知识点**:`Collection` 继承 `Iterable`、`iterator()` / `hasNext()` / `next()`、for+get 遍历与迭代器遍历对比 **场景描述**:所有单列集合都继承自 `Iterable` 接口,都具备迭代能力。通过迭代器,可以**统一**地遍历 ArrayList 和 LinkedList,且与集合的内部结构无关。 **题目要求**: 在 `IteratorDemo` 类的 `main` 方法中,完成以下操作: ```java import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; public class IteratorDemo { public static void main(String[] args) { // 1. 创建一个 ArrayList,添加 5 个元素:"A", "B", "C", "D", "E" // 2. 方式一:使用 for + size() + get(i) 遍历 ArrayList,并输出 // 提示:for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } // 3. 方式二:通过 list.iterator() 获取迭代器对象 // 使用 while + hasNext() + next() 遍历,并输出 // 提示:Iterator iterator = list.iterator(); // while (iterator.hasNext()) { String element = iterator.next(); ... } // 4. 再创建一个 LinkedList,添加同样的 5 个元素 // 5. 使用迭代器遍历 LinkedList(同样的模板代码) // 观察:LinkedList 没有 get(i) 高效随机访问,迭代器是它的最佳遍历方式 } } ``` **预期输出**: ``` ========== for 循环遍历 ArrayList ========== A B C D E ========== 迭代器遍历 ArrayList ========== A B C D E ========== 迭代器遍历 LinkedList ========== A B C D E ``` **思考题**: 1. `hasNext()` 和 `next()` 分别做什么?`next()` 调用后迭代器指针会发生什么变化? 2. 为什么 LinkedList 用 `for + get(i)` 遍历效率低,而迭代器遍历效率高?(提示:`get(i)` 每次从头遍历,O(n²)) 3. 增强 for(foreach)的底层实现是什么?它和迭代器有什么关系? --- ## 练习 2:Map 集合基本操作(HashMap 键值对) **难度**:⭐⭐ **知识点**:`HashMap` 创建、`put()` / `get()` / `containsKey()` / `containsValue()` / `size()` / `isEmpty()` / `remove()` / `clear()`、键不可重复 **场景描述**:Map 是**双列集合**,以「键值对」为单位存储。键(Key)不可重复,值(Value)可以重复。模拟一个"学生信息登记表"——用学号作为键,姓名作为值。 **题目要求**: 在 `MapDemo` 类的 `main` 方法中,完成以下操作: ```java import java.util.HashMap; import java.util.Map; public class MapDemo { public static void main(String[] args) { // 1. 创建一个 HashMap,学号作为 Key,姓名作为 Value // 提示:Map map = new HashMap<>(); // 2. 登记 4 名学生:x001-张三, x002-李四, x003-王五, x004-赵六 // 使用 put() 方法 // 3. 输出集合大小(size()):"学生人数:4" // 4. 根据学号查询姓名(get("x002")):"学号 x002 的学生:李四" // 5. 判断是否包含某个键(containsKey("x001")):true // 判断是否包含某个值(containsValue("王五")):true // 判断是否包含不存在的键(containsKey("x999")):false // 6. 重复键测试:put("x001", "张三丰") // 再次输出"学号 x001 的学生":观察发生了什么? // —— 提示:同一个 Key 再次 put 会【覆盖】旧值,键不可重复 // 7. 移除一个学生(remove("x004")),输出移除后的集合大小 // 8. 判断集合是否为空(isEmpty()):false // 9. 清空集合(clear()),再次判断 isEmpty():true } } ``` **预期输出**: ``` 学生人数:4 学号 x002 的学生:李四 包含键 x001:true 包含值 王五:true 包含键 x999:false 重复键 put 后,学号 x001 的学生:张三丰 ← 旧值"张三"被覆盖 移除 x004 后,学生人数:3 集合为空?false 清空后,集合为空?true ``` **思考题**: 1. 为什么 Map 的 Key 不能重复?重复 put 会怎样?(提示:覆盖旧值) 2. `containsKey()` 和 `containsValue()` 的底层实现有什么区别?(提示:containsKey 走哈希定位,containsValue 需遍历) 3. 为什么 `keySet()` 返回的是 Set 而不是 List?(提示:键不可重复) --- ## 练习 3:Map 集合的遍历(keySet + get / entrySet) **难度**:⭐⭐ **知识点**:`keySet()` + `get(key)`(方式一)、`entrySet()` + `getKey()` / `getValue()`(方式二)、`Map.Entry` 内部类型 **场景描述**:Map 没有索引,不能像 List 那样用 `for + get(i)` 直接遍历。有两种标准遍历方式:**keySet + get**(先取键再反查值)和 **entrySet**(直接取键值对)。 **题目要求**: 在 `MapTraversalDemo` 类的 `main` 方法中,完成以下操作: ```java import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapTraversalDemo { public static void main(String[] args) { // 1. 创建一个 HashMap,存储商品价格 // "苹果"-3.5, "香蕉"-2.0, "西瓜"-6.8, "葡萄"-12.5 // 2. 方式一:keySet() + foreach + get(key) 遍历 // 输出格式:"key:苹果,value:3.5" // 步骤: // a. Set keys = map.keySet(); // b. for (String key : keys) { Double value = map.get(key); ... } // 3. 方式二:entrySet() + foreach + getKey()/getValue() 遍历 // 输出格式:"key:苹果,value:3.5" // 步骤: // a. Set> entries = map.entrySet(); // b. for (Map.Entry me : entries) { // String key = me.getKey(); Double value = me.getValue(); ... } // 4. 统计商品总价(遍历两种方式均可),输出:"商品总价:24.8" } } ``` **预期输出**(顺序因 HashMap 无序而不定): ``` ========== 方式一:keySet + get ========== key:香蕉,value:2.0 key:苹果,value:3.5 key:葡萄,value:12.5 key:西瓜,value:6.8 ========== 方式二:entrySet ========== key:香蕉,value:2.0 key:苹果,value:3.5 key:葡萄,value:12.5 key:西瓜,value:6.8 ========== 总价统计 ========== 商品总价:24.8 ``` **关键对比**: | 遍历方式 | 步骤 | 特点 | |----------|------|------| | 方式一:`keySet()` + `get(key)` | 取键集合 → 遍历键 → 反查值 | **间接**:每次 get 都是一次哈希查找 | | 方式二:`entrySet()` | 取键值对集合 → 遍历 → getKey/getValue | **直接**:一次拿到键和值,效率更高 | **思考题**: 1. 方式二为什么比方式一高效?(提示:方式一每次 `get(key)` 都要重新哈希查找) 2. `Map.Entry` 是什么类型?它和 Map 是什么关系?(提示:Map 的内部类型) 3. 两次遍历的输出顺序是否与 put 的顺序一致?为什么?(提示:HashMap 无序) --- # 第二部分:进阶挑战 > 以下练习综合运用 Map 集合、哈希表原理、自定义类型等知识。 --- ## 进阶 1:自定义类作为 HashMap 的 Key(重写 hashCode / equals) **难度**:⭐⭐⭐ **知识点**:HashMap 依赖 `hashCode()` 和 `equals()` 保证键唯一、自定义类型作为 Key、哈希表结构(数组+链表) **场景描述**:HashMap 的键 Key 的唯一性依赖 `hashCode()` 和 `equals()` 两个方法。**自定义类型**想存入 HashMap 的 Key 中,**必须重写**这两个方法,否则两个属性相同的对象会被当作不同的键。 **题目要求**: 定义 `Student` 类(学号 + 姓名),作为 HashMap 的 Key,并重写 `hashCode()` 和 `equals()`: ```java import java.util.HashMap; import java.util.Map; import java.util.Objects; public class Student { private String id; // 学号 private String name; // 姓名 public Student(String id, String name) { this.id = id; this.name = name; } // TODO: 补全 getter / setter 方法 // TODO: 重写 hashCode() // 规则:属性值完全相同时,必须返回相同的哈希值 // 提示:return Objects.hash(id, name); // TODO: 重写 equals(Object o) // 规则:id 和 name 都相同时返回 true // 提示:使用 Objects.equals(id, student.id) 判断 @Override public String toString() { return "Student{id='" + id + "', name='" + name + "'}"; } } ``` 编写 `StudentKeyTest` 测试类: ```java import java.util.HashMap; import java.util.Map; public class StudentKeyTest { public static void main(String[] args) { Map map = new HashMap<>(); // 1. 创建两个属性完全相同的 Student 对象 Student s1 = new Student("x001", "张三"); Student s2 = new Student("x001", "张三"); // 2. 判断 s1 和 s2 是否相等(不重写 equals 时是 false) System.out.println("s1.equals(s2):" + s1.equals(s2)); // 期望 true // 3. 判断 s1 和 s2 的哈希值是否相同 System.out.println("s1.hashCode():" + s1.hashCode()); System.out.println("s2.hashCode():" + s2.hashCode()); // 期望相同 // 4. 分别用 s1、s2 作为 Key 存入 map.put(s1, "学员A"); map.put(s2, "学员B"); // 5. 思考:如果不重写 hashCode/equals,size 是几?重写后 size 是几? System.out.println("map 大小:" + map.size()); // 期望 1(s1、s2 视为同一个键) // 6. 用 get 取出:new 一个属性相同的对象 s3 去查找 Student s3 = new Student("x001", "张三"); System.out.println("用 s3 查找:" + map.get(s3)); // 期望 "学员B" } } ``` **预期输出**: ``` s1.equals(s2):true s1.hashCode():1416729608 ← 具体数值不重要,关键是 s2 相同 s2.hashCode():1416729608 map 大小:1 用 s3 查找:学员B ``` **原理分析(必答)**: 1. 如果不重写 `hashCode()` 和 `equals()`,`map.size()` 会是多少?为什么? - 答:会是 2。因为默认 `hashCode()` 基于对象地址,两个 new 出来的对象地址不同 → 哈希值不同 → 存到不同位置;`equals()` 默认用 `==` 比较地址,也判定不相等 2. HashMap 在存 Key 时,先调用哪个方法、后调用哪个方法? - 答:先计算 `hashCode()` 定位存储位置(数组下标),若位置已有元素,再用 `equals()` 比较是否相同——相同则不存(覆盖),不同则挂到链表上 3. 为什么重写 `equals()` 时必须同步重写 `hashCode()`?(提示:哈希表的一致性契约) --- ## 进阶 2:Map 综合应用 —— 单词词频统计器 **难度**:⭐⭐⭐⭐ **知识点**:Map 键值对建模、`put` / `get` / `containsKey` / 遍历、HashMap 统计思想、综合应用 **场景描述**:词频统计是 Map 最典型的应用场景——**单词作为 Key,出现次数作为 Value**。遍历文本时:单词没出现过就 `put(word, 1)`,出现过就 `put(word, 次数+1)`。 **题目要求**: 编写 `WordCountDemo` 类,完成单词词频统计: ```java import java.util.HashMap; import java.util.Map; import java.util.Scanner; import java.util.Set; public class WordCountDemo { public static void main(String[] args) { // 1. 定义一个字符串数组,模拟一段文本中的单词(含重复) // "apple", "banana", "apple", "orange", "banana", "apple", "grape" // 2. 创建一个 HashMap,单词 → 出现次数 // 3. 遍历单词数组,统计每个单词出现的次数 // 核心逻辑: // for (String word : words) { // if (map.containsKey(word)) { // map.put(word, map.get(word) + 1); // 已出现过,次数 +1 // } else { // map.put(word, 1); // 第一次出现 // } // } // 4. 使用 entrySet() 遍历输出统计结果 // 输出格式:"单词:apple,出现次数:3" // 5. 进阶:找出出现次数最多的单词 // 提示:遍历 entrySet,用变量记录 maxCount 和对应单词 // 6. 拓展:使用 Scanner 从键盘输入一句话(用空格分隔),统计每个单词的出现次数 // 提示:String[] inputWords = scanner.nextLine().split(" "); } } ``` **预期输出**: ``` ========== 词频统计结果 ========== 单词:grape,出现次数:1 单词:banana,出现次数:2 单词:orange,出现次数:1 单词:apple,出现次数:3 出现次数最多的单词:apple(3 次) ``` **测试用例**(Scanner 输入部分): ``` 请输入一段英文(单词用空格分隔):I love java and I love coding I:2 love:2 java:1 and:1 coding:1 ``` **拓展思考**: 1. 如果把统计逻辑换成 `map.put(word, map.getOrDefault(word, 0) + 1)`,能简化代码吗?`getOrDefault` 是什么意思? 2. 如果想按出现次数从高到低排序输出,可以怎么做?(提示:把 entrySet 转为 List 后用 Comparator 排序) 3. 如果文本有 100 万个单词,HashMap 相比数组统计的优势在哪里?(提示:键是字符串不是数字索引,HashMap 用哈希直接定位) --- # 参考答案要点 ## 练习 1 参考 ```java import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; public class IteratorDemo { public static void main(String[] args) { ArrayList list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); list.add("D"); list.add("E"); System.out.println("========== for 循环遍历 ArrayList =========="); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } System.out.println("========== 迭代器遍历 ArrayList =========="); Iterator iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } LinkedList linkedList = new LinkedList<>(); linkedList.add("A"); linkedList.add("B"); linkedList.add("C"); linkedList.add("D"); linkedList.add("E"); System.out.println("========== 迭代器遍历 LinkedList =========="); Iterator iterator1 = linkedList.iterator(); while (iterator1.hasNext()) { System.out.println(iterator1.next()); } } } ``` ## 练习 2 参考 ```java Map map = new HashMap<>(); map.put("x001", "张三"); map.put("x002", "李四"); map.put("x003", "王五"); map.put("x004", "赵六"); System.out.println("学生人数:" + map.size()); System.out.println("学号 x002 的学生:" + map.get("x002")); System.out.println("包含键 x001:" + map.containsKey("x001")); System.out.println("包含值 王五:" + map.containsValue("王五")); System.out.println("包含键 x999:" + map.containsKey("x999")); map.put("x001", "张三丰"); // 键重复 → 覆盖旧值 System.out.println("重复键 put 后,学号 x001 的学生:" + map.get("x001")); map.remove("x004"); System.out.println("移除 x004 后,学生人数:" + map.size()); System.out.println("集合为空?" + map.isEmpty()); map.clear(); System.out.println("清空后,集合为空?" + map.isEmpty()); ``` ## 练习 3 参考 ```java Map map = new HashMap<>(); map.put("苹果", 3.5); map.put("香蕉", 2.0); map.put("西瓜", 6.8); map.put("葡萄", 12.5); System.out.println("========== 方式一:keySet + get =========="); Set keys = map.keySet(); for (String key : keys) { Double value = map.get(key); System.out.println("key:" + key + ",value:" + value); } System.out.println("========== 方式二:entrySet =========="); Set> entries = map.entrySet(); for (Map.Entry me : entries) { System.out.println("key:" + me.getKey() + ",value:" + me.getValue()); } // 总价统计(方式二) double total = 0; for (Map.Entry me : map.entrySet()) { total += me.getValue(); } System.out.println("========== 总价统计 =========="); System.out.println("商品总价:" + total); ``` ## 进阶 1 参考 ```java // Student 类:重写 hashCode / equals @Override public int hashCode() { return Objects.hash(id, name); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return Objects.equals(id, student.id) && Objects.equals(name, student.name); } ``` ## 进阶 2 参考(核心逻辑) ```java String[] words = {"apple", "banana", "apple", "orange", "banana", "apple", "grape"}; Map map = new HashMap<>(); for (String word : words) { if (map.containsKey(word)) { map.put(word, map.get(word) + 1); } else { map.put(word, 1); } } // 使用 getOrDefault 简化 for (String word : words) { map.put(word, map.getOrDefault(word, 0) + 1); } // 找出出现次数最多的单词 String maxWord = null; int maxCount = 0; for (Map.Entry me : map.entrySet()) { if (me.getValue() > maxCount) { maxCount = me.getValue(); maxWord = me.getKey(); } } System.out.println("出现次数最多的单词:" + maxWord + "(" + maxCount + " 次)"); ``` --- # 涵盖知识点总览 | 知识点 | 对应练习 | 说明 | |--------|---------|------| | Iterator 迭代器遍历 | 练习 1 | `iterator()` / `hasNext()` / `next()`、Iterable 接口 | | for + get 遍历 vs 迭代器 | 练习 1 | 索引遍历只适合 List,迭代器通用所有单列集合 | | HashMap 创建与键值对操作 | 练习 2 | `put` / `get` / `containsKey` / `containsValue` / `size` / `isEmpty` / `remove` / `clear` | | 键不可重复特性 | 练习 2 | 重复 put 覆盖旧值 | | Map 遍历方式一 | 练习 3 | `keySet()` + foreach + `get(key)`(间接遍历) | | Map 遍历方式二 | 练习 3 | `entrySet()` + foreach + `getKey()` / `getValue()`(直接遍历,效率更高) | | `Map.Entry` 内部类型 | 练习 3 | 一个 Entry 代表一个键值对 | | HashMap 键唯一性契约 | 进阶 1 | 依赖 `hashCode()` 定位 + `equals()` 比较,二者必须同时重写 | | 自定义类型作为 Key | 进阶 1 | 属性相同 → 哈希值相同 → equals 相同 → 视为同一键 | | 哈希表结构(数组+链表/红黑树) | 进阶 1 | 树化条件:数组长度>64 且链表节点>8;退化条件:节点<6 | | Map 综合统计应用 | 进阶 2 | 单词词频统计、`getOrDefault`、找最大值 | | HashMap 无序性 | 练习 2、3 | 存储顺序 ≠ 添加顺序(哈希定位) |