# 2026-07-27 内部类(Inner Class)基础练习(课堂练习用) > **说明**:以下习题围绕今日授课核心内容——**内部类(Inner Class)** 展开,涵盖成员内部类、私有内部类的设计原则、静态成员内部类、局部内部类、匿名内部类等知识点。 > > 题目分为两部分:**随堂练习**(内部类基础,7 题)和 **进阶练习**(综合挑战,3 题)。 --- # 第一部分:随堂练习 > 以下练习围绕内部类的基础概念和用法,由浅入深。 --- ## 练习 1:内部类入门——汽车与发动机 **难度**:⭐ **知识点**:内部类概念、成员内部类定义、内部类访问外部类成员 **场景描述**:汽车(`Car`)包含发动机(`Engine`),发动机是汽车的内部组成部分,离开汽车无法独立存在。发动机有自己的行为(启动、熄火),同时需要知道所属汽车的信息。 **`Car` 类**(外部类): - **私有属性**:`brand`(品牌,String)、`model`(型号,String) - **构造方法**:`Car(String brand, String model)` - **成员内部类** `Engine`: - **普通方法**:`void start()` —— 输出 `"发动机启动:{brand} {model} 轰鸣声..."`(直接访问外部类的私有属性) - **普通方法**:`void stop()` —— 输出 `"发动机熄火:{brand} {model} 安静了..."` - **外部类方法**:`void drive()` —— 创建 `Engine` 对象并调用 `start()`,然后输出 `"{brand} {model} 正在行驶..."`,再调用 `stop()` **测试类 `CarTest` 要求**: ```java public class CarTest { public static void main(String[] args) { // 1. 创建 Car 对象 Car car = new Car("宝马", "X5"); // 2. 调用外部类方法——内部由 Car 自己完成 car.drive(); // 3. (思考)如果 Engine 不是内部类,而是独立类, // 需要如何修改才能让 Engine 访问 Car 的 brand 和 model? } } ``` **预期输出**: ``` 发动机启动:宝马 X5 轰鸣声... 宝马 X5 正在行驶... 发动机熄火:宝马 X5 安静了... ``` **思考题**: 1. 从上述代码看,内部类访问外部类的私有成员时,是否需要 getter 方法? 2. 如果 `Engine` 改为私有内部类,外界(如 `CarTest`)能否直接创建 `Engine` 对象? 3. 本练习中 `Engine` 对象的创建是在 `Car` 的内部方法 `drive()` 中完成的,这符合「外部类访问内部类必须创建对象」的特点吗? --- ## 练习 2:私有内部类设计——人(Person)与心脏(Heart) **难度**:⭐⭐ **知识点**:私有内部类、外部类提供公共方法暴露内部类功能、设计原则 **场景描述**:人有心脏,心脏离开人体无法独立存在。按照 **私有内部类设计原则**,心脏应该作为人的私有内部类,外界必须通过人的公共方法才能使用心脏的功能。 **`Person` 类**(外部类): - **私有属性**:`name`(姓名,String) - **构造方法**:`Person(String name)` - **私有内部类** `Heart`: - **普通方法**:`void beat()` —— 输出 `"{name} 的心脏在跳动:咚咚咚..."`(直接访问外部类私有属性) - **普通方法**:`void rest()` —— 输出 `"{name} 的心脏在休息:咚...咚...咚..."` - **公共方法**:`void heartbeat()` —— 在方法内部创建 `Heart` 对象并调用 `beat()` 和 `rest()` **测试类 `PersonTest` 要求**: ```java public class PersonTest { public static void main(String[] args) { // 1. 创建 Person 对象 Person person = new Person("张三"); // 2. 通过 Person 的公共方法访问心脏功能 person.heartbeat(); // 3. ❌ 思考:能直接这样创建心脏对象吗? // Person.Heart h = new Person().new Heart(); // 编译能通过吗?为什么? } } ``` **预期输出**: ``` 张三 的心脏在跳动:咚咚咚... 张三 的心脏在休息:咚...咚...咚... ``` **请回答以下问题**: 1. 将 `Heart` 类设为 `private` 的好处是什么? 2. 如果 `Person` 类想对外提供「获取心脏对象」的能力,应该如何修改代码?(参考课堂笔记中 `getHeart()` 方法的设计) 3. 如果不使用私有内部类,而是将 `Heart` 写成一个独立类,让 `Person` 持有 `Heart` 成员变量,会有什么弊端?(提示:外界可能跳过 `Person` 直接创建 `Heart` 对象) **代码补充挑战**:请将 `Person` 类补充完整: ```java public class Person { private String name; // 构造方法 public Person(String name) { this.name = name; } // 私有内部类 Heart // 请补充代码 // 公共方法 heartbeat() // 请补充代码 } ``` --- ## 练习 3:静态成员内部类——学校与学生证打印 **难度**:⭐⭐ **知识点**:静态成员内部类、静态内部类创建格式、静态内部类与外部类的关系 **场景描述**:每所学校(`School`)都有统一格式的学生证(`StudentCard`),其中包含学校名称和学生的个人信息。学生证的模板格式由学校统一规定(静态内部类),不依赖某个具体的学校对象。 **`School` 类**(外部类): - **私有静态属性**:`schoolName`(学校名称,static String)—— 所有学校对象共享 - **静态代码块**:初始化 `schoolName` 为 `"四福科技大学"` - **静态成员内部类** `StudentCard`:用 `static` 修饰 - **私有属性**:`studentName`(学生姓名,String)、`studentId`(学号,String) - **构造方法**:`StudentCard(String studentName, String studentId)` - **普通方法**:`void showCard()` —— 输出学生证信息(学校名称使用外部类的静态属性) **测试类 `SchoolTest` 要求**: ```java public class SchoolTest { public static void main(String[] args) { // 1. 创建静态内部类对象——格式:外部类名.内部类名 变量 = new 外部类名.内部类名(...) School.StudentCard card1 = new School.StudentCard("张三", "2024001"); School.StudentCard card2 = new School.StudentCard("李四", "2024002"); // 2. 输出学生证信息 card1.showCard(); card2.showCard(); // 3. 思考:创建静态内部类对象时,需不需要先创建 School 对象?为什么? } } ``` **预期输出**: ``` ========== 四福科技大学 学生证 ========== 姓名:张三 学号:2024001 ========== 四福科技大学 学生证 ========== 姓名:李四 学号:2024002 ``` **思考题**: 1. 静态内部类和普通成员内部类在创建对象时的格式有什么区别? 2. 静态内部类能否访问外部类的 **非静态** 成员(如实例变量)?为什么? 3. 如果 `schoolName` 不是 `static` 的,上面的代码能否正常运行?应该如何修改? --- ## 练习 4:局部内部类——餐厅点餐系统 **难度**:⭐⭐⭐ **知识点**:局部内部类、定义在方法内部的类、局部内部类的作用域 **场景描述**:餐厅(`Restaurant`)中有多种点餐方式(如堂食、外卖)。每种点餐方式的实现细节(如输出欢迎语、计算价格等)各不相同,且只在点餐方法内部使用,故设计为局部内部类。 **`Restaurant` 类**(外部类): - **私有属性**:`name`(餐厅名称,String) - **构造方法**:`Restaurant(String name)` - **方法** `void dineIn()` —— 堂食点餐 - **局部内部类** `DineInOrder`: - **构造方法**:输出 `"【堂食】xxx 餐厅欢迎您入座!"`(xxx 为餐厅名称) - **普通方法**:`void orderFood(String dish)` —— 输出 `"【堂食】已点餐:{dish},请稍候..."` - **普通方法**:`void pay(double amount)` —— 输出 `"【堂食】支付:{amount} 元,感谢惠顾!"` - 在 `dineIn()` 方法内创建 `DineInOrder` 对象,调用 `orderFood("红烧肉")` 和 `pay(68.0)` - **方法** `void takeout()` —— 外卖点餐 - **局部内部类** `TakeoutOrder`: - **构造方法**:输出 `"【外卖】感谢您选择 xxx 餐厅!"`(xxx 为餐厅名称) - **普通方法**:`void orderFood(String dish)` —— 输出 `"【外卖】已接单:{dish},正在准备..."` - **普通方法**:`void pay(double amount)` —— 输出 `"【外卖】在线支付:{amount} 元,等待配送..."` - 在 `takeout()` 方法内创建 `TakeoutOrder` 对象,调用 `orderFood("宫保鸡丁")` 和 `pay(38.0)` **测试类 `RestaurantTest` 要求**: ```java public class RestaurantTest { public static void main(String[] args) { Restaurant restaurant = new Restaurant("幸福小馆"); // 堂食 restaurant.dineIn(); System.out.println(); // 外卖 restaurant.takeout(); // 思考:在外界能否直接创建 DineInOrder 或 TakeoutOrder 对象?为什么? } } ``` **预期输出**: ``` 【堂食】幸福小馆 餐厅欢迎您入座! 【堂食】已点餐:红烧肉,请稍候... 【堂食】支付:68.0 元,感谢惠顾! 【外卖】感谢您选择 幸福小馆 餐厅! 【外卖】已接单:宫保鸡丁,正在准备... 【外卖】在线支付:38.0 元,等待配送... ``` **思考题**: 1. 局部内部类的作用范围是什么?方法外部能否访问? 2. 局部内部类除了可以访问外部类的成员,还可以访问所在方法内的 **局部变量**。请在 `dineIn()` 方法中添加一个局部变量 `String welcome = "欢迎光临";`,尝试在 `DineInOrder` 内部类中访问它,观察是否能编译通过。 3. 局部内部类和成员内部类在使用场景上有什么本质区别? --- ## 练习 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 接口(){}.方法()` | --- # 第二部分:进阶练习 > 以下为综合挑战题,融合多种内部类类型、内部类访问规则、设计原则等知识点。 --- ## 第 1 题:综合应用——计算机与外设(成员内部类 + 静态内部类)⭐⭐⭐⭐ ### 需求说明 模拟计算机(`Computer`)的组成结构。计算机包含: - **CPU** —— 计算机的核心处理部件,每台计算机有自己的 CPU,CPU 的行为与具体的计算机相关(**成员内部类**) - **USB 接口规范** —— 所有计算机的 USB 接口规范是统一的,不依赖具体的计算机实例(**静态内部类**) - **内存条** —— 计算机的内存,可更换升级(**普通成员内部类**,但设计为私有) ### 类设计 **`Computer` 类**(外部类): **私有属性**: | 属性 | 类型 | 说明 | |------|------|------| | `brand` | String | 品牌 | | `model` | String | 型号 | | `ram` | Memory | 内存条对象(内部类) | **构造方法**:`Computer(String brand, String model, int ramSize)` —— 初始化品牌、型号,并创建 `Memory` 对象(内存大小为 `ramSize` GB) #### 内部类 1:CPU(成员内部类,非私有) **`CPU` 类**(成员内部类): - **私有属性**:`cores`(核心数,int)、`frequency`(主频,double,单位 GHz) - **构造方法**:`CPU(int cores, double frequency)` - **普通方法**:`void process()` —— 输出 `"{brand} {model} CPU 正在计算:{cores} 核 {frequency}GHz 全速运行..."`(访问外部类私有属性) #### 内部类 2:UsbSpec(静态成员内部类) **`UsbSpec` 类**(静态成员内部类): - **静态常量**:`USB_VERSION = "3.0"`(String) - **静态常量**:`MAX_SPEED = "5Gbps"`(String) - **静态方法**:`void showSpec()` —— 输出 `"USB 接口规范:版本 {USB_VERSION},最大速度 {MAX_SPEED}"` - **普通方法**:`void connectDevice(String deviceName)` —— 输出 `"已连接 {deviceName},使用 USB {USB_VERSION} 协议传输数据"` #### 内部类 3:Memory(私有成员内部类) **`Memory` 类**(私有成员内部类): - **私有属性**:`size`(容量,int,单位 GB) - **构造方法**:`Memory(int size)` - **普通方法**:`void loadData()` —— 输出 `"{brand} {model} 正在加载数据到内存({size}GB)..."` #### 外部类方法 | 方法 | 说明 | |------|------| | `void startUp()` | 启动计算机:创建 CPU 对象(4 核,3.2GHz),调用 CPU 的 `process()` 方法,再调用 `Memory` 的 `loadData()` 方法 | | `void replaceMemory(int newSize)` | 更换内存条:重新创建 `Memory` 对象,输出 `"内存已升级为 {newSize}GB"` | | `CPU getCpu()` | 获取 CPU 对象(返回 CPU 类型,演示外部类返回内部类对象) | ### 测试类要求 **`ComputerTest` 类**: ```java public class ComputerTest { public static void main(String[] args) { // 1. 创建 Computer 对象(品牌:联想,型号:ThinkPad,内存:16GB) Computer computer = new Computer("联想", "ThinkPad", 16); // 2. 启动计算机 computer.startUp(); System.out.println(); // 3. 更换内存(升级到 32GB) computer.replaceMemory(32); System.out.println(); // 4. 使用静态内部类 UsbSpec // 4.1 调用静态内部类的静态方法(无需创建对象) Computer.UsbSpec.showSpec(); System.out.println(); // 4.2 创建静态内部类对象,调用普通方法 Computer.UsbSpec usb = new Computer.UsbSpec(); usb.connectDevice("移动硬盘"); usb.connectDevice("键盘"); System.out.println(); // 5. 通过外部类方法获取 CPU 对象(演示外部类返回内部类对象) Computer.CPU cpu = computer.getCpu(); cpu.process(); } } ``` ### 预期输出 ``` 联想 ThinkPad CPU 正在计算:4 核 3.2GHz 全速运行... 联想 ThinkPad 正在加载数据到内存(16GB)... 内存已升级为 32GB USB 接口规范:版本 3.0,最大速度 5Gbps 已连接 移动硬盘,使用 USB 3.0 协议传输数据 已连接 键盘,使用 USB 3.0 协议传输数据 联想 ThinkPad CPU 正在计算:4 核 3.2GHz 全速运行... ``` ### 思考题 1. 本练习中为什么 `UsbSpec` 设计为 **静态内部类**?USB 接口规范和具体的计算机实例有关吗? 2. 为什么 `Memory` 设计为 **私有内部类**?外界可以直接更换内存吗?(提示:通过 `replaceMemory()` 方法) 3. 成员内部类 `CPU` 没有被 `private` 修饰,外界可以直接创建 `Computer.CPU` 对象吗?如果可以,创建格式是什么? 4. 如果将 `UsbSpec` 改为普通成员内部类(去掉 `static`),创建 `UsbSpec` 对象的格式会变成什么样? --- ## 第 2 题:综合应用——银行账户系统(私有内部类 + 局部内部类 + 静态内部类)⭐⭐⭐⭐⭐ ### 需求说明 模拟银行账户系统。一个银行(`Bank`)管理多个账户(`Account`),每个账户包含交易记录(`Transaction`)。要求: - **账户** 作为银行的 **私有成员内部类** —— 外界不能直接创建账户对象,必须通过银行创建 - **交易记录** 作为账户方法的 **局部内部类** —— 只在存款/取款方法内部使用 - **利率信息** 作为银行的 **静态内部类** —— 所有银行共享利率规范 ### 类设计 **`Bank` 类**(外部类): **私有静态属性**: | 属性 | 类型 | 说明 | |------|------|------| | `bankName` | String | 银行名称(静态) | **静态代码块**:初始化 `bankName = "四福银行"` **私有成员内部类 `Account`**: - **私有属性**:`accountId`(账号,String)、`holderName`(户主姓名,String)、`balance`(余额,double) - **构造方法**:`Account(String accountId, String holderName, double initialBalance)` - **方法** `void deposit(double amount)` —— 存款 - 方法内部定义 **局部内部类** `DepositTransaction`: - **构造方法**:无参,输出 `"【存款交易】开始..."` - **方法**:`void execute()` —— 增加余额,输出 `"{holderName} 存入 {amount} 元,余额:{balance} 元(银行:{bankName})"`(注意:局部内部类可访问外部类所有成员,也可访问所在方法的局部变量 `amount`) - 方法内部创建 `DepositTransaction` 对象并调用 `execute()` - **方法** `void withdraw(double amount)` —— 取款 - 如果余额不足,输出 `"取款失败:{holderName} 余额不足(当前余额:{balance} 元)"` - 否则,方法内部定义 **局部内部类** `WithdrawTransaction`: - **构造方法**:无参,输出 `"【取款交易】开始..."` - **方法**:`void execute()` —— 减少余额,输出 `"{holderName} 取出 {amount} 元,余额:{balance} 元"` - 余额足够时创建 `WithdrawTransaction` 对象并调用 `execute()` **方法** `Account openAccount(String holderName, double initialBalance)` —— 开户 - 自动生成账号(格式:`"AC" + System.currentTimeMillis()`) - 创建 `Account` 对象并返回 - 输出 `"开户成功!欢迎 {holderName} 成为 {bankName} 的客户"` **静态内部类 `RateInfo`**: - **静态常量**:`SAVING_RATE = 0.025`(年利率 2.5%) - **静态常量**:`LOAN_RATE = 0.045`(贷款利率 4.5%) - **静态方法**:`void showRate()` —— 输出利率信息 - **普通方法**:`double calculateInterest(double principal, int years)` —— 计算存款利息(复利:`principal * Math.pow(1 + SAVING_RATE, years) - principal`) ### 测试类要求 **`BankTest` 类**: ```java public class BankTest { public static void main(String[] args) { // 1. 查看银行利率(静态内部类的静态方法) Bank.RateInfo.showRate(); System.out.println(); // 2. 创建利率计算器对象(静态内部类) Bank.RateInfo calculator = new Bank.RateInfo(); double interest = calculator.calculateInterest(10000, 3); System.out.println("1 万元存 3 年,利息:" + String.format("%.2f", interest) + " 元"); System.out.println(); // 3. 到银行开户 Bank bank = new Bank(); // 注意:openAccount 返回的是 Bank.Account 类型(私有内部类), // 但因为 Account 是 private 的,这里需要思考怎么接收返回值? // 提示:可以用 var(Java 10+)或 Object 类型接收,或者让 Bank 提供其他方法来操作账户 // ❓ 问题:由于 Account 是私有内部类,BankTest 中无法声明 Bank.Account 类型的变量! // 那么应该如何设计 Bank 类,让外界能够操作账户? // // 提示方案: // 方案 A:在 Bank 中存储账户,并提供存款/取款的公共方法(通过账号索引) // 方案 B:定义一个接口,让 Account 实现该接口,Bank 返回接口类型 // // 请选择一种方案完成设计,并在测试类中完成以下操作: // - 张三开户,存入 5000 元 // - 张三存款 2000 元 // - 张三取款 1000 元 // - 张三尝试取款 10000 元(余额不足) } } ``` ### 思考与设计挑战 本题的关键在于:**私有内部类 `Account` 的类型在外界不可见**,如何让外界操作账户? 请选择以下方案之一完成设计: **方案 A**(推荐):在 `Bank` 类中使用数组或集合存储账户,对外提供 `deposit(String accountId, double amount)` 和 `withdraw(String accountId, double amount)` 等公共方法,外界通过账号来操作。 **方案 B**:定义一个 `Accountable` 接口(包含 `deposit`、`withdraw` 方法),让 `Account` 实现该接口,`openAccount()` 方法返回 `Accountable` 类型。 ### 预期输出(方案 A 参考) ``` ========== 四福银行 利率信息 ========== 储蓄年利率:2.5% 贷款年利率:4.5% 1 万元存 3 年,利息:768.91 元 ========== 开户与交易 ========== 开户成功!欢迎 张三 成为 四福银行 的客户 【存款交易】开始... 张三 存入 5000.0 元,余额:5000.0 元(银行:四福银行) 【存款交易】开始... 张三 存入 2000.0 元,余额:7000.0 元(银行:四福银行) 【取款交易】开始... 张三 取出 1000.0 元,余额:6000.0 元 取款失败:张三 余额不足(当前余额:6000.0 元) ``` ### 思考题 1. 为什么 `Account` 设计为 `Bank` 的私有内部类?这体现了什么设计原则?(参考课堂笔记中 `Heart` 私有化的原因) 2. 局部内部类 `DepositTransaction` 和 `WithdrawTransaction` 为什么定义在方法内部,而不是定义为成员内部类? 3. 静态内部类 `RateInfo` 中的 `showRate()` 是静态方法,不需要创建对象就能调用;而 `calculateInterest()` 是普通方法,需要创建对象才能调用。这两种方式分别适用于什么场景? 4. 如果去掉 `RateInfo` 的 `static` 修饰,上面的代码中 `Bank.RateInfo.showRate()` 还能编译通过吗?`new Bank.RateInfo()` 还能编译通过吗? --- ## 第 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` 接口和 `Collections.sort()` 方法有什么相似之处? 5. 如果将来想按「成绩相同则按年龄排序」这种复合条件排序,需要修改哪些代码? --- ## 📋 题目总览 | 序号 | 所属部分 | 核心知识点 | 难度 | |------|---------|-----------|------| | 练习 1 | 随堂 | 成员内部类概念、内部类访问外部类成员 | ⭐ | | 练习 2 | 随堂 | 私有内部类设计原则、外部类提供公共方法暴露内部类功能 | ⭐⭐ | | 练习 3 | 随堂 | 静态成员内部类、创建格式、与外部类的关系 | ⭐⭐ | | 练习 4 | 随堂 | 局部内部类、定义在方法内部、作用域限制 | ⭐⭐⭐ | | 练习 5 | 随堂 | 匿名内部类入门、多态接收方式、基于接口/抽象类 | ⭐⭐ | | 练习 6 | 随堂 | 匿名内部类作为方法参数传递(开发中最常用方式) | ⭐⭐⭐ | | 练习 7 | 随堂 | 匿名内部类三种使用方式对比、匿名对象直接调用 | ⭐⭐⭐ | | 进阶 1 | 进阶 | 成员内部类 + 静态内部类 + 私有内部类综合运用 | ⭐⭐⭐⭐ | | 进阶 2 | 进阶 | 私有内部类 + 局部内部类 + 静态内部类 + 设计模式综合 | ⭐⭐⭐⭐⭐ | | 进阶 3 | 进阶 | 匿名内部类 + 接口回调 + 排序/筛选策略模式(学生管理系统) | ⭐⭐⭐⭐⭐ |