20260727-内部类练习.md 22 KB

2026-07-27 内部类(Inner Class)基础练习(课堂练习用)

说明:以下习题围绕今日授课核心内容——内部类(Inner Class) 展开,涵盖成员内部类、私有内部类的设计原则、静态成员内部类、局部内部类等知识点。

题目分为两部分:随堂练习(内部类基础,4 题)和 进阶练习(综合挑战,2 题)。


第一部分:随堂练习

以下练习围绕内部类的基础概念和用法,由浅入深。


练习 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 要求

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 要求

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 类补充完整:

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 要求

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 要求

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. 局部内部类和成员内部类在使用场景上有什么本质区别?

第二部分:进阶练习

以下为综合挑战题,融合多种内部类类型、内部类访问规则、设计原则等知识点。


第 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() 方法,再调用 MemoryloadData() 方法
void replaceMemory(int newSize) 更换内存条:重新创建 Memory 对象,输出 "内存已升级为 {newSize}GB"
CPU getCpu() 获取 CPU 对象(返回 CPU 类型,演示外部类返回内部类对象)

测试类要求

ComputerTest

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

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 接口(包含 depositwithdraw 方法),让 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. 局部内部类 DepositTransactionWithdrawTransaction 为什么定义在方法内部,而不是定义为成员内部类?
  3. 静态内部类 RateInfo 中的 showRate() 是静态方法,不需要创建对象就能调用;而 calculateInterest() 是普通方法,需要创建对象才能调用。这两种方式分别适用于什么场景?
  4. 如果去掉 RateInfostatic 修饰,上面的代码中 Bank.RateInfo.showRate() 还能编译通过吗?new Bank.RateInfo() 还能编译通过吗?

📋 题目总览

序号 所属部分 核心知识点 难度
练习 1 随堂 成员内部类概念、内部类访问外部类成员
练习 2 随堂 私有内部类设计原则、外部类提供公共方法暴露内部类功能 ⭐⭐
练习 3 随堂 静态成员内部类、创建格式、与外部类的关系 ⭐⭐
练习 4 随堂 局部内部类、定义在方法内部、作用域限制 ⭐⭐⭐
进阶 1 进阶 成员内部类 + 静态内部类 + 私有内部类综合运用 ⭐⭐⭐⭐
进阶 2 进阶 私有内部类 + 局部内部类 + 静态内部类 + 设计模式综合 ⭐⭐⭐⭐⭐