Browse Source

feat: 新增匿名内部类课堂练习题(练习5-7 + 进阶3),更新README记录

WanJL 19 hours ago
parent
commit
af34ae5a4c
2 changed files with 503 additions and 3 deletions
  1. 1 1
      README.md
  2. 502 2
      课堂练习题/20260727-内部类练习.md

+ 1 - 1
README.md

@@ -64,5 +64,5 @@
 | 2026-07-22 | [继承练习](./课堂练习题/20260722-继承练习.md) | 继承概念与格式、多层继承、super() 调用父类构造方法、方法重写、is...a 关系判断、继承+构造方法+对象数组+排序+多态综合 |
 | 2026-07-23 | [抽象类与接口练习](./课堂练习题/20260723-抽象类与接口练习.md) | 抽象类定义与特点、抽象方法重写、抽象类计算周长与面积(Shape体系)、抽象类+对象数组+排序+多态综合、模板方法设计模式;接口入门(Playable播放器接口、Charger充电规范继承+实现、SmartPhone实现多接口) |
 | 2026-07-24 | [多态练习](./课堂练习题/20260724-多态练习.md) | 多态概念与三前提、多态成员访问特点(编译看左运行看右)、向上转型/向下转型、instanceof运算符、多态+对象数组+排序综合(员工绩效管理系统)、接口多态+Usb设备管理器 |
-| 2026-07-27 | [内部类练习](./课堂练习题/20260727-内部类练习.md) | 成员内部类、内部类直接访问外部类私有成员、私有内部类设计原则、静态成员内部类创建格式、局部内部类作用域与访问规则、三种内部类综合运用(计算机外设+银行账户系统) |
+| 2026-07-27 | [内部类练习](./课堂练习题/20260727-内部类练习.md) | 成员内部类、私有内部类设计原则、静态成员内部类、局部内部类、匿名内部类三种使用方式(多态接收/方法参数/匿名对象直接调用)、匿名内部类 + 接口回调 + 排序/筛选策略模式(学生信息管理系统) |
 

+ 502 - 2
课堂练习题/20260727-内部类练习.md

@@ -1,8 +1,8 @@
 # 2026-07-27 内部类(Inner Class)基础练习(课堂练习用)
 
-> **说明**:以下习题围绕今日授课核心内容——**内部类(Inner Class)** 展开,涵盖成员内部类、私有内部类的设计原则、静态成员内部类、局部内部类等知识点。
+> **说明**:以下习题围绕今日授课核心内容——**内部类(Inner Class)** 展开,涵盖成员内部类、私有内部类的设计原则、静态成员内部类、局部内部类、匿名内部类等知识点。
 >
-> 题目分为两部分:**随堂练习**(内部类基础,4 题)和 **进阶练习**(综合挑战,2 题)。
+> 题目分为两部分:**随堂练习**(内部类基础,7 题)和 **进阶练习**(综合挑战,3 题)。
 
 ---
 
@@ -241,6 +241,312 @@ public class RestaurantTest {
 
 ---
 
+## 练习 5:匿名内部类入门——多态接收方式(USB 设备 + 计算器)
+
+**难度**:⭐⭐  
+**知识点**:匿名内部类概念、使用前提、语法格式、多态接收(变量赋值)
+
+**场景描述**:不使用独立的实现类,直接通过匿名内部类创建 USB 设备和计算器对象。匿名内部类的本质是 **继承了某个类或实现了某个接口的子类的匿名对象**,通过多态接收赋值给父类/接口类型的变量。
+
+### 5.1 基于接口的匿名内部类(Usb)
+
+参考课堂 `Usb` 接口和 `Demo01.java`,使用匿名内部类创建 USB 设备。
+
+**`Usb` 接口**(复用课堂代码):
+- **抽象方法**:`void startUsb()` —— 启动设备
+- **抽象方法**:`void closeUsb()` —— 关闭设备
+
+**要求**:
+
+```java
+public class DemoTest {
+    public static void main(String[] args) {
+        // 1. 使用匿名内部类创建 Usb 对象(鼠标)
+        //    提示:Usb usb = new Usb() { ... };
+        //    请补充代码:
+        Usb usb = ;
+        usb.startUsb();
+        usb.closeUsb();
+        
+        System.out.println();
+        
+        // 2. 再创建一个 Usb 匿名内部类对象(键盘)
+        //    输出:"键盘已连接,USB 接口启动"
+        //    输出:"键盘已断开,USB 接口关闭"
+        //    请补充代码:
+        
+    }
+}
+```
+
+**预期输出**:
+```
+鼠标已连接,USB 接口启动
+鼠标已断开,USB 接口关闭
+
+键盘已连接,USB 接口启动
+键盘已断开,USB 接口关闭
+```
+
+### 5.2 基于抽象类的匿名内部类(Compute)
+
+参考课堂 `Compute` 抽象类,使用匿名内部类实现加法计算器。
+
+**`Compute` 抽象类**(复用课堂代码):
+- **抽象方法**:`int add(int a, int b)` —— 两数相加
+
+**要求**:
+
+```java
+// 使用匿名内部类创建 Compute 对象,实现加法计算
+Compute compute = new Compute() {
+    // 请补充代码
+};
+int result = compute.add(10, 20);
+System.out.println("10 + 20 = " + result);
+```
+
+**预期输出**:
+```
+10 + 20 = 30
+```
+
+### 思考题
+
+1. 匿名内部类的前提条件是什么?为什么必须存在一个类或接口?
+2. 匿名内部类的本质是什么?它的 `.class` 文件名会是什么样子的?
+3. 语法格式中最后的分号 `;` 能否省略?为什么?
+4. 匿名内部类和之前学的局部内部类有什么区别?
+
+---
+
+## 练习 6:匿名内部类做方法参数——餐具 + USB 设备(开发中最常用方式)
+
+**难度**:⭐⭐⭐  
+**知识点**:匿名内部类作为方法参数传递、减少独立实现类文件、开发中最常用
+
+**场景描述**:在开发中,如果某个接口的实现类只想在当前场景使用一次,不想被其他人复用,可以直接在方法参数位置创建匿名内部类,避免创建独立的实现类文件。本练习通过「餐具使用」和「USB 设备接入」两个场景来练习这种最常用的方式。
+
+### 6.1 Person 吃饭使用餐具
+
+参考课堂 `Tableware` 接口、`Person` 类和 `Test02.java`。
+
+**`Tableware` 接口**(复用课堂代码):
+- **抽象方法**:`void openTableware()` —— 打开餐具
+- **抽象方法**:`void useTableware()` —— 使用餐具
+
+**`Person` 类**(复用课堂代码):
+
+```java
+public class Person {
+    /**
+     * 吃饭
+     * @param tableware 吃饭使用的餐具
+     */
+    public void eat(Tableware tableware) {
+        tableware.openTableware();
+        tableware.useTableware();
+        System.out.println("开始干饭......");
+    }
+}
+```
+
+**要求**:
+
+```java
+public class EatTest {
+    public static void main(String[] args) {
+        Person person = new Person();
+        
+        // 方式一:先创建匿名内部类对象,再传入方法
+        // 请补充代码:创建 Tableware 的匿名内部类(一次性筷子)
+        Tableware tableware = ;  // 补全
+        person.eat(tableware);
+        
+        System.out.println();
+        
+        // 方式二(推荐):直接在方法参数中创建匿名内部类(一次性勺子)
+        // 这种写法更紧凑,开发中最常用!
+        person.eat(/* 请在此位置直接创建 Tableware 的匿名内部类 */);
+    }
+}
+```
+
+**预期输出**:
+```
+打开一次性筷子
+使用一次性筷子
+开始干饭......
+
+打开一次性勺子
+使用一次性勺子
+开始干饭......
+```
+
+### 6.2 NoteBook 接入 USB 设备
+
+参考课堂 `NoteBook` 类和 `Test01.java`。
+
+**`NoteBook` 类**(复用课堂代码):
+
+```java
+public class NoteBook {
+    public void useUSB(Usb usb) {
+        usb.startUsb();
+        usb.closeUsb();
+    }
+}
+```
+
+**要求**:不创建任何独立的实现类,完全使用匿名内部类完成以下操作。
+
+```java
+public class NoteBookTest {
+    public static void main(String[] args) {
+        NoteBook nb = new NoteBook();
+        
+        // 1. 使用方式一:多态接收 + 传入方法
+        //    创建 Usb 匿名内部类(U盘)
+        //    输出:"U盘已插入,正在读取数据..."
+        //    输出:"U盘已安全弹出"
+        // 请补充代码
+        
+        
+        // 2. 使用方式二(推荐):直接作为参数传递
+        //    直接在 useUSB() 参数中创建 Usb 匿名内部类(移动硬盘)
+        //    输出:"移动硬盘已连接,传输速率 5Gbps"
+        //    输出:"移动硬盘已断开连接"
+        nb.useUSB(/* 请在此直接创建匿名内部类 */);
+        
+        System.out.println();
+        
+        // 3. 尝试接入第三个设备(摄像头),同样使用匿名内部类
+        nb.useUSB(/* 请在此直接创建匿名内部类 */);
+    }
+}
+```
+
+**预期输出**:
+```
+U盘已插入,正在读取数据...
+U盘已安全弹出
+
+移动硬盘已连接,传输速率 5Gbps
+移动硬盘已断开连接
+
+摄像头已启动,正在捕获画面...
+摄像头已关闭,停止录制
+```
+
+### 思考题
+
+1. 使用匿名内部类作为方法参数有什么好处?(对比先创建独立实现类再传入的方式)
+2. 什么场景下适合使用匿名内部类?什么场景下应该创建独立的实现类?
+3. 如果 `Tableware` 接口有 5 个抽象方法,使用匿名内部类实现时有何感受?这说明了什么设计原则?
+
+---
+
+## 练习 7:匿名内部类三种使用方式对比——多功能计算器
+
+**难度**:⭐⭐⭐  
+**知识点**:匿名内部类的三种使用方式对比、匿名对象直接调用、特有方法
+
+**场景描述**:通过一个多功能计算器案例,对比匿名内部类的三种使用方式。理解它们各自的适用场景和优缺点。
+
+**`Calculator` 接口**:
+- **抽象方法**:`int add(int a, int b)` —— 加法
+- **抽象方法**:`int subtract(int a, int b)` —— 减法
+
+**`AdvCalculator` 抽象类**:
+- **抽象方法**:`int multiply(int a, int b)` —— 乘法
+- **抽象方法**:`int divide(int a, int b)` —— 除法
+- **普通方法**:`void showInfo()` —— 输出 `"高级计算器"`
+- **特有方法**:`void reset()` —— 输出 `"计算器已重置"`(注意:这是一个普通方法,不是抽象方法)
+
+**要求**:完成以下测试类,对比三种方式。
+
+```java
+public class CalculatorTest {
+    public static void main(String[] args) {
+        // ============ 方式一:多态接收(变量赋值) ============
+        // 特点:变量可重复使用,但只能调用父类/接口中声明的方法
+        // 要求:创建 Calculator 接口的匿名内部类,实现加法和减法
+        
+        Calculator calc = new Calculator() {
+            // 请补充实现
+        };
+        System.out.println("加法:" + calc.add(30, 12));      // 42
+        System.out.println("减法:" + calc.subtract(30, 12)); // 18
+        // calc.reset();  // ❌ 思考:这行能编译通过吗?为什么?
+        
+        System.out.println();
+        
+        // ============ 方式二:作为方法参数传递 ============
+        // 特点:最常用,代码紧凑,适合一次性使用
+        // 要求:定义一个 useCalculator 方法,接收 Calculator 参数,
+        //       调用 add 和 subtract 并输出结果
+        
+        useCalculator(new Calculator() {
+            // 请补充实现(计算 100 + 200 和 100 - 200)
+        });
+        
+        System.out.println();
+        
+        // ============ 方式三:匿名对象直接调用 ============
+        // 特点:可以调用匿名内部类中定义的特有方法
+        // 要求:创建 AdvCalculator 抽象类的匿名内部类,并直接调用方法
+        
+        // 在匿名内部类中重写 multiply() 和 divide()
+        // 同时定义一个特有方法:void showMemory() —— 输出 "内存已清空"
+        // 然后通过匿名对象直接调用 multiply()、divide()、showMemory()
+        
+        new AdvCalculator() {
+            // 请补充代码
+        }. /* 直接调用方法 */;
+    }
+    
+    // 方式二的辅助方法
+    public static void useCalculator(Calculator calc) {
+        System.out.println("使用计算器:");
+        System.out.println("加法结果:" + calc.add(100, 200));
+        System.out.println("减法结果:" + calc.subtract(100, 200));
+        System.out.println("计算完毕!");
+    }
+}
+```
+
+### 预期输出
+
+```
+============ 方式一:多态接收 ============
+加法:42
+减法:18
+
+============ 方式二:方法参数 ============
+使用计算器:
+加法结果:300
+减法结果:-100
+计算完毕!
+
+============ 方式三:匿名对象直接调用 ============
+乘法:15 x 8 = 120
+除法:15 ÷ 8 = 1
+内存已清空
+```
+
+### 对比总结填空
+
+请根据以上三种方式,完成下面的对比表格:
+
+| 对比维度 | 方式一:多态接收 | 方式二:方法参数 | 方式三:匿名对象直接调用 |
+|---------|----------------|----------------|----------------------|
+| 能否重复使用 | ①______ | 不能(一次性) | ②______ |
+| 能否调用特有方法 | ③______ | ④______ | ⑤______ |
+| 开发中使用频率 | 较少 | ⑥______ | 极少 |
+| 典型代码结构 | `接口 变量 = new 接口(){}.方法` | `方法名(new 接口(){})` | `new 接口(){}.方法()` |
+
+---
+
 # 第二部分:进阶练习
 
 > 以下为综合挑战题,融合多种内部类类型、内部类访问规则、设计原则等知识点。
@@ -489,6 +795,196 @@ public class BankTest {
 
 ---
 
+## 第 3 题:综合应用——学生信息管理系统(匿名内部类 + 排序 + 筛选 + 接口回调)⭐⭐⭐⭐⭐
+
+### 需求说明
+
+开发一个学生信息管理系统,使用 `Student` 实体类,通过 **匿名内部类** 实现多种排序和筛选逻辑。重点练习匿名内部类作为方法参数传递的典型应用(策略模式雏形)。
+
+要求:
+- **不创建任何 StudentComparator 或 StudentFilter 的实现类**
+- 所有比较逻辑和筛选逻辑全部使用 **匿名内部类** 实现
+- 体验「行为参数化」的思想——将行为(排序/筛选)作为参数传递给方法
+
+### 类设计
+
+**`Student` 类**(参考课堂 Student 类,增加 `score` 属性):
+
+```java
+public class Student {
+    private String id;      // 学号
+    private String name;    // 姓名
+    private Integer age;    // 年龄
+    private Double score;   // 成绩
+
+    // 构造方法:Student(String id, String name, Integer age, Double score)
+    // getter/setter
+    // toString() 返回格式:Student{id='xxx', name='xxx', age=xx, score=xx}
+}
+```
+
+**需要自行定义的接口**:
+
+**`Comparator` 接口**(比较器):
+- **抽象方法**:`int compare(Student s1, Student s2)`
+- 返回负数表示 s1 < s2,返回正数表示 s1 > s2,返回 0 表示相等
+
+**`Filter` 接口**(筛选器):
+- **抽象方法**:`boolean accept(Student student)`
+- 返回 `true` 表示保留该学生,`false` 表示过滤掉
+
+**`StudentManager` 类**(业务类,由学生自行实现):
+
+| 方法 | 说明 |
+|------|------|
+| `void sortStudents(Student[] students, Comparator comparator)` | 使用冒泡排序按指定比较器对学生数组排序 |
+| `void printStudents(Student[] students)` | 遍历输出所有学生信息 |
+| `Student[] filterStudents(Student[] students, Filter filter)` | 按指定条件筛选学生,返回筛选后的新数组 |
+| `Student findMax(Student[] students, Comparator comparator)` | 按指定比较器查找「最大」学生 |
+
+### 测试类要求
+
+**`StudentSystemTest` 类**:创建至少 6 个学生对象,放入数组,然后使用匿名内部类完成以下操作:
+
+```java
+public class StudentSystemTest {
+    public static void main(String[] args) {
+        // 1. 创建学生数组
+        Student[] students = new Student[]{
+            new Student("S001", "张三", 20, 85.5),
+            new Student("S002", "李四", 22, 92.0),
+            new Student("S003", "王五", 19, 76.5),
+            new Student("S004", "赵六", 21, 88.0),
+            new Student("S005", "孙七", 23, 95.5),
+            new Student("S006", "周八", 20, 68.0)
+        };
+
+        StudentManager manager = new StudentManager();
+
+        System.out.println("========== 原始学生信息 ==========");
+        manager.printStudents(students);
+
+        // ========== 任务 1:按成绩从高到低排序 ==========
+        // 使用匿名内部类实现 Comparator 接口(成绩降序)
+        manager.sortStudents(students, new Comparator() {
+            // 请补充代码
+        });
+        System.out.println("\n========== 任务 1:按成绩从高到低排序 ==========");
+        manager.printStudents(students);
+
+        // ========== 任务 2:按年龄从小到大排序 ==========
+        // 使用匿名内部类实现 Comparator 接口(年龄升序)
+        // 请补充代码
+
+
+        // ========== 任务 3:按姓名长度从短到长排序 ==========
+        // 提示:s1.getName().length() - s2.getName().length()
+        // 使用匿名内部类实现 Comparator 接口
+        // 请补充代码
+
+
+        // ========== 任务 4:筛选成绩 >= 85 的优秀学生 ==========
+        // 使用匿名内部类实现 Filter 接口
+        Student[] excellentStudents = manager.filterStudents(students, new Filter() {
+            // 请补充代码
+        });
+        System.out.println("\n========== 任务 4:筛选成绩 >= 85 的优秀学生 ==========");
+        manager.printStudents(excellentStudents);
+
+        // ========== 任务 5:筛选年龄 > 20 的学生 ==========
+        // 使用匿名内部类实现 Filter 接口
+        // 请补充代码
+
+
+        // ========== 任务 6:查找成绩最高的学生 ==========
+        // 使用匿名内部类实现 Comparator 接口,配合 findMax() 方法
+        Student topStudent = manager.findMax(students, new Comparator() {
+            // 请补充代码
+        });
+        System.out.println("\n========== 任务 6:成绩最高的学生 ==========");
+        System.out.println(topStudent);
+    }
+}
+```
+
+### 预期输出(参考)
+
+```
+========== 原始学生信息 ==========
+Student{id='S001', name='张三', age=20, score=85.5}
+Student{id='S002', name='李四', age=22, score=92.0}
+Student{id='S003', name='王五', age=19, score=76.5}
+Student{id='S004', name='赵六', age=21, score=88.0}
+Student{id='S005', name='孙七', age=23, score=95.5}
+Student{id='S006', name='周八', age=20, score=68.0}
+
+========== 任务 1:按成绩从高到低排序 ==========
+Student{id='S005', name='孙七', age=23, score=95.5}
+Student{id='S002', name='李四', age=22, score=92.0}
+Student{id='S004', name='赵六', age=21, score=88.0}
+Student{id='S001', name='张三', age=20, score=85.5}
+Student{id='S003', name='王五', age=19, score=76.5}
+Student{id='S006', name='周八', age=20, score=68.0}
+
+========== 任务 2:按年龄从小到大排序 ==========
+Student{id='S003', name='王五', age=19, score=76.5}
+Student{id='S001', name='张三', age=20, score=85.5}
+Student{id='S006', name='周八', age=20, score=68.0}
+Student{id='S004', name='赵六', age=21, score=88.0}
+Student{id='S002', name='李四', age=22, score=92.0}
+Student{id='S005', name='孙七', age=23, score=95.5}
+
+========== 任务 3:按姓名长度排序 ==========
+Student{id='S003', name='王五', age=19, score=76.5}
+Student{id='S002', name='李四', age=22, score=92.0}
+Student{id='S001', name='张三', age=20, score=85.5}
+Student{id='S004', name='赵六', age=21, score=88.0}
+Student{id='S005', name='孙七', age=23, score=95.5}
+Student{id='S006', name='周八', age=20, score=68.0}
+
+========== 任务 4:筛选成绩 >= 85 的优秀学生 ==========
+Student{id='S001', name='张三', age=20, score=85.5}
+Student{id='S002', name='李四', age=22, score=92.0}
+Student{id='S004', name='赵六', age=21, score=88.0}
+Student{id='S005', name='孙七', age=23, score=95.5}
+
+========== 任务 5:筛选年龄 > 20 的学生 ==========
+Student{id='S002', name='李四', age=22, score=92.0}
+Student{id='S004', name='赵六', age=21, score=88.0}
+Student{id='S005', name='孙七', age=23, score=95.5}
+
+========== 任务 6:成绩最高的学生 ==========
+Student{id='S005', name='孙七', age=23, score=95.5}
+```
+
+### 代码补充挑战
+
+请补全 `StudentManager` 类中的 `sortStudents` 方法:
+
+```java
+public void sortStudents(Student[] students, Comparator comparator) {
+    // 使用冒泡排序
+    for (int i = 0; i < students.length - 1; i++) {
+        for (int j = 0; j < students.length - 1 - i; j++) {
+            // 如果 students[j] > students[j+1](根据 comparator 判断),则交换
+            // 提示:if (comparator.compare(students[j], students[j+1]) > 0) { 交换 }
+            // 请补充代码
+
+        }
+    }
+}
+```
+
+### 思考题
+
+1. 本练习中,排序和筛选的逻辑是通过什么方式传入方法的?这体现了什么设计思想?
+2. 如果不用匿名内部类,每次排序都需要创建一个独立的实现类(如 `ScoreComparator`、`AgeComparator` 等),会有什么弊端?
+3. `Comparator` 接口的 `compare()` 方法返回负数、零、正数分别代表什么含义?
+4. 本练习的做法与 Java 标准库中的 `Comparator<T>` 接口和 `Collections.sort()` 方法有什么相似之处?
+5. 如果将来想按「成绩相同则按年龄排序」这种复合条件排序,需要修改哪些代码?
+
+---
+
 ## 📋 题目总览
 
 | 序号 | 所属部分 | 核心知识点 | 难度 |
@@ -497,5 +993,9 @@ public class BankTest {
 | 练习 2 | 随堂 | 私有内部类设计原则、外部类提供公共方法暴露内部类功能 | ⭐⭐ |
 | 练习 3 | 随堂 | 静态成员内部类、创建格式、与外部类的关系 | ⭐⭐ |
 | 练习 4 | 随堂 | 局部内部类、定义在方法内部、作用域限制 | ⭐⭐⭐ |
+| 练习 5 | 随堂 | 匿名内部类入门、多态接收方式、基于接口/抽象类 | ⭐⭐ |
+| 练习 6 | 随堂 | 匿名内部类作为方法参数传递(开发中最常用方式) | ⭐⭐⭐ |
+| 练习 7 | 随堂 | 匿名内部类三种使用方式对比、匿名对象直接调用 | ⭐⭐⭐ |
 | 进阶 1 | 进阶 | 成员内部类 + 静态内部类 + 私有内部类综合运用 | ⭐⭐⭐⭐ |
 | 进阶 2 | 进阶 | 私有内部类 + 局部内部类 + 静态内部类 + 设计模式综合 | ⭐⭐⭐⭐⭐ |
+| 进阶 3 | 进阶 | 匿名内部类 + 接口回调 + 排序/筛选策略模式(学生管理系统) | ⭐⭐⭐⭐⭐ |