# 2026-07-24 多态(Polymorphism)基础练习(课堂练习用) > **说明**:以下习题围绕今日授课核心内容——**多态(Polymorphism)** 展开,涵盖多态的概念与前提、多态成员访问特点、向上转型/向下转型、instanceof 运算符、多态与抽象类/接口的结合。 > > 题目分为两部分:**随堂练习**(多态基础,4 题)和 **进阶练习**(综合挑战,2 题)。 --- # 第一部分:随堂练习 > 以下练习围绕多态的基础概念和用法,由浅入深。 --- ## 练习 1:多态入门——Pet 宠物喂食体系 **难度**:⭐ **知识点**:多态概念、多态三个前提条件、父类引用指向子类对象 创建一个宠物喂食体系,体验 **同一个方法调用,不同子类表现出不同的行为**。 **`Pet` 类**(父类): - **普通方法**:`void eat()` —— 输出 `"宠物在吃饭..."` **`Dog` 类**(继承 Pet): - **重写** `eat()` —— 输出 `"小狗在啃骨头..."` **`Cat` 类**(继承 Pet): - **重写** `eat()` —— 输出 `"小猫在吃鱼..."` **测试类 `PetTest` 要求**: ```java public class PetTest { public static void main(String[] args) { // 1. 使用父类引用指向子类对象(多态) Pet p1 = new Dog(); Pet p2 = new Cat(); // 2. 调用 eat() 方法——运行期执行子类的重写方法 p1.eat(); p2.eat(); // 3. 思考:如果 Pet pet = new Pet(); 可以吗?使用 new Pet() 有什么缺点? } } ``` **预期输出**: ``` 小狗在啃骨头... 小猫在吃鱼... ``` **思考题**: 1. 以上代码体现了多态的哪个前提条件? 2. 如果 `eat()` 方法没有被重写,输出会是什么?为什么? 3. 如果 `Pet` 类中还有 `sleep()` 方法但 `Dog` 和 `Cat` 没有重写它,通过 `p1.sleep()` 调用会执行哪个类的 `sleep()`? --- ## 练习 2:多态成员访问特点——理解「编译看左边,运行看右边」 **难度**:⭐⭐ **知识点**:多态中成员变量和成员方法的访问特点、编译期 vs 运行期 通过代码验证多态的成员访问规则。 **`交通工具` 体系**: **`Vehicle` 类**(父类): - **成员变量**:`String name = "交通工具"` - **普通方法**:`void run()` —— 输出 `"交通工具在行驶..."` - **普通方法**:`void stop()` —— 输出 `"交通工具刹车停止"` - **静态方法**:`static void info()` —— 输出 `"Vehicle 静态方法"` **`Car` 类**(继承 Vehicle): - **成员变量**:`String name = "小汽车"` - **重写** `run()` —— 输出 `"小汽车在高速行驶..."` - **特有方法**:`void openSunroof()` —— 输出 `"打开天窗~"` **测试类要求**: 编写 `VehicleTest` 类,创建一个 `Car` 对象,分别使用「父类引用指向子类对象」和「子类引用指向子类对象」两种方式,观察成员变量、成员方法、特有方法的访问差异。 **模板代码**: ```java public class VehicleTest { public static void main(String[] args) { // 情况一:父类引用指向子类对象(多态) Vehicle v = new Car(); System.out.println(v.name); // 输出什么? v.run(); // 输出什么? v.stop(); // 输出什么? // v.openSunroof(); // 能否调用?编译能通过吗? // 情况二:子类引用指向子类对象(普通) Car c = new Car(); System.out.println(c.name); // 输出什么? c.run(); // 输出什么? c.openSunroof(); // 能否调用? // 情况三:静态方法 v.info(); // 调用谁的静态方法? c.info(); // 调用谁的静态方法? Vehicle.info(); // 推荐哪种调用方式? } } ``` **请写出你的预期输出**(先思考,再运行验证): ``` v.name → __________ v.run() → __________ v.stop() → __________ c.name → __________ c.run() → __________ v.info() → __________ ``` **总结填空**: 1. 成员变量:编译看______,运行看______ 2. 成员方法(非静态):编译看______,运行看______ 3. 静态方法:编译看______,运行看______(因为静态方法属于______,不参与多态) 4. 通过父类引用,______(能/不能)调用子类的特有方法 --- ## 练习 3:向下转型 + instanceof——动物园管理员 **难度**:⭐⭐⭐ **知识点**:向上转型、向下转型、instanceof 运算符、子类特有方法的调用 **场景描述**:动物园里有多种动物,管理员需要根据不同的动物执行不同的操作。有些动物会游泳,有些动物会飞翔,这些是特有行为。 **`Animal` 类**(父类): - **普通方法**:`void eat()` —— 输出 `"动物在吃东西"` - **普通方法**:`void sleep()` —— 输出 `"动物在睡觉"` **`Fish` 类**(继承 Animal): - **重写** `eat()` —— 输出 `"鱼儿在吃饲料"` - **重写** `sleep()` —— 输出 `"鱼儿睁着眼睛睡觉"` - **特有方法**:`void swim()` —— 输出 `"鱼儿在水中游来游去~"` **`Bird` 类**(继承 Animal): - **重写** `eat()` —— 输出 `"小鸟在啄米"` - **重写** `sleep()` —— 输出 `"小鸟在巢穴里睡觉"` - **特有方法**:`void fly()` —— 输出 `"小鸟在天空中飞翔~"` **请完成 `ZooKeeper` 类**: ```java public class ZooKeeper { // 这个方法接收 Animal 类型的参数(多态) // 要求: // 1. 调用 eat() 和 sleep() // 2. 使用 instanceof 判断是哪种动物 // 3. 如果是 Fish,向下转型并调用 swim() // 4. 如果是 Bird,向下转型并调用 fly() public void handleAnimal(Animal animal) { // 请补充代码 } public static void main(String[] args) { ZooKeeper keeper = new ZooKeeper(); // 使用向上转型创建对象 Animal a1 = new Fish(); Animal a2 = new Bird(); // 交给管理员处理 keeper.handleAnimal(a1); keeper.handleAnimal(a2); } } ``` **预期输出**: ``` 鱼儿在吃饲料 鱼儿睁着眼睛睡觉 鱼儿在水中游来游去~ 小鸟在啄米 小鸟在巢穴里睡觉 小鸟在天空中飞翔~ ``` **思考题**: 1. 如果调用 `keeper.handleAnimal(new Animal())` 会发生什么?`instanceof` 判断会进入哪个分支? 2. 如果去掉 `instanceof` 判断,直接强制转型 `(Fish) animal`,当传入 `Bird` 对象时会怎样? 3. 为什么需要向下转型?向上转型后直接调用 `animal.swim()` 可以吗?为什么? --- ## 练习 4:判断多态题型——哪些属于多态应用 **难度**:⭐⭐ **知识点**:多态的判断、多态与普通继承的区别 判断以下代码片段中,**哪些体现了多态**,哪些没有,并说明理由。 | 序号 | 代码片段 | 是否多态? | 理由 | |------|---------|-----------|------| | 1 | `Animal a = new Dog(); a.eat();` | | | | 2 | `Dog d = new Dog(); d.eat();` | | | | 3 | `Usb usb = new Mouse(); noteBook.useUsb(usb);` | | | | 4 | `Teacher t = new Teacher(); t.play();` | | | | 5 | `Person p = new Student(); ((Student)p).study();` | | | | 6 | `Object obj = "Hello";` | | | **拓展思考**: ```java // 以下代码是否是多态?为什么? public void feed(Pet pet) { pet.eat(); } // 调用 feed(new Dog()); feed(new Cat()); ``` **参考答案思路**: | 序号 | 是否多态? | 理由 | |------|-----------|------| | 1 | ✅ 是 | 父类引用指向子类对象,调用重写方法 —— 满足多态三前提 | | 2 | ❌ 否 | 子类引用指向子类对象,没有「父类引用指向子类对象」 | | 3 | ✅ 是 | 接口引用指向实现类对象,属于接口多态 | | 4 | ❌ 否 | 没有父类引用,没有方法重写的调用 | | 5 | ✅ 是 | 先向上转型(多态),再向下转型恢复子类完整形态 | | 6 | ✅ 是 | String 是 Object 的子类,属于继承多态 | --- # 第二部分:进阶练习 > 以下为综合挑战题,融合多态、向上转型/向下转型、instanceof、对象数组和排序等知识点。 --- ## 第 1 题:多态 + 对象数组 + instanceof——员工绩效管理系统 ⭐⭐⭐⭐ ### 需求说明 某公司有三种类型的员工:**正式员工**(全职)、**兼职员工**(按时计薪)和 **实习生**(固定津贴)。每种员工的薪资计算方式不同,且各有特有行为。 使用 **多态数组** 存储所有员工,遍历时通过 `instanceof` 判断类型并向下转型,调用特有方法。 ### 类设计 **`Employee` 抽象类**(父类): - **私有属性**:`id`(工号,int)、`name`(姓名,String) - **有参构造方法**:`Employee(int id, String name)` - **抽象方法**:`double calculateSalary()` —— 计算薪资 - **普通方法**:`void showInfo()` —— 输出 `"工号:xxx,姓名:xxx,薪资:xxx"`(调用 `calculateSalary()` 获得薪资) **`FullTimeEmployee` 类**(继承 Employee): - **私有属性**:`baseSalary`(基本工资,double)、`bonus`(绩效奖金,double) - **构造方法**:`FullTimeEmployee(int id, String name, double baseSalary, double bonus)` - **重写** `calculateSalary()` —— 返回 `baseSalary + bonus * 1.2`(绩效上浮 20%) - **特有方法**:`void attendMeeting()` —— 输出 `"正式员工 xxx 参加周会..."`(xxx 为姓名) **`PartTimeEmployee` 类**(继承 Employee): - **私有属性**:`hourlyRate`(时薪,double)、`hoursWorked`(工作小时数,int) - **构造方法**:`PartTimeEmployee(int id, String name, double hourlyRate, int hoursWorked)` - **重写** `calculateSalary()` —— 返回 `hourlyRate * hoursWorked` - **特有方法**:`void clockIn()` —— 输出 `"兼职员工 xxx 打卡上班..."`(xxx 为姓名) **`Intern` 类**(继承 Employee): - **私有属性**:`allowance`(实习津贴,double) - **构造方法**:`Intern(int id, String name, double allowance)` - **重写** `calculateSalary()` —— 返回 `allowance`(固定津贴,无其他扣减) - **特有方法**:`void learnSkill()` —— 输出 `"实习生 xxx 在学习新技能..."`(xxx 为姓名) ### 测试类要求 **`EmployeeManager` 类**: 1. **创建员工数组**:创建一个 `Employee[]` 数组,包含至少 5 名员工(各类型混合) ```java // 示例数据 Employee emp1 = new FullTimeEmployee(1001, "张三", 8000, 2000); Employee emp2 = new PartTimeEmployee(2001, "李四", 50, 80); Employee emp3 = new Intern(3001, "王五", 2000); Employee emp4 = new FullTimeEmployee(1002, "赵六", 10000, 3000); Employee emp5 = new PartTimeEmployee(2002, "孙七", 45, 60); ``` 2. **遍历数组输出基本信息**:调用每个元素的 `showInfo()` 方法 3. **遍历数组,按类型执行特有行为**: - 如果是 `FullTimeEmployee` → 向下转型 → 调用 `attendMeeting()` - 如果是 `PartTimeEmployee` → 向下转型 → 调用 `clockIn()` - 如果是 `Intern` → 向下转型 → 调用 `learnSkill()` 4. **计算总薪资成本**:遍历数组,累加所有员工的 `calculateSalary()`,输出总薪资 5. **查找薪资最高的员工**:遍历数组,找出 `calculateSalary()` 最大的员工,输出其信息 ### 思考题 1. `Employee[]` 数组中存储的是不同类型对象,这体现了面向对象哪个特征? 2. 如果不用 `instanceof` 判断类型,直接向下转型会有什么风险? 3. 如果新增一个 `Contractor`(外包人员)类,需要修改哪些代码?这些修改是否符合「开闭原则」? ### 预期输出(参考) ``` ========== 员工薪资信息 ========== 工号:1001,姓名:张三,薪资:10400.00 工号:2001,姓名:李四,薪资:4000.00 工号:3001,姓名:王五,薪资:2000.00 工号:1002,姓名:赵六,薪资:13600.00 工号:2002,姓名:孙七,薪资:2700.00 ========== 执行特有行为 ========== 正式员工 张三 参加周会... 兼职员工 李四 打卡上班... 实习生 王五 在学习新技能... 正式员工 赵六 参加周会... 兼职员工 孙七 打卡上班... ========== 薪资统计 ========== 总薪资成本:32700.00 元 薪资最高的员工:工号:1002,姓名:赵六,薪资:13600.00 ``` > **提示**: > - 数组类型声明为 `Employee[]`,存储不同子类对象——这是多态的数组应用 > - 使用 `instanceof` 判断具体类型后再向下转型,是安全调用特有方法的标准做法 > - 薪资计算使用 `double` 类型,输出时保留两位小数使用 `System.out.printf("%.2f", value)` --- ## 第 2 题:综合应用——Usb 设备管理器(多态 + 接口 + instanceof)⭐⭐⭐⭐⭐ ### 需求说明 基于课堂上的 Usb 接口案例,扩展实现一个 Usb 设备管理器。要求: - `NoteBook` 类可以接入多个 Usb 设备(使用 Usb 数组存储) - 不同 Usb 设备有不同的特有功能 - 使用 `instanceof` 判断设备类型,调用特有方法 ### 类设计 **`Usb` 接口**(复用课堂代码): - **抽象方法**:`void startUsb()` —— 启动设备 - **抽象方法**:`void stopUsb()` —— 停止设备 **`Mouse` 类**(实现 Usb): - **私有属性**:`brand`(品牌,String) - **构造方法**:`Mouse(String brand)` - **实现** `startUsb()` —— 输出 `"鼠标已连接(品牌:xxx)"` - **实现** `stopUsb()` —— 输出 `"鼠标已断开"` - **特有方法**:`void click()` —— 输出 `"鼠标单击——嗒"` **`Keyboard` 类**(实现 Usb): - **私有属性**:`layout`(键盘布局,String,如 "QWERTY" / "AZERTY") - **构造方法**:`Keyboard(String layout)` - **实现** `startUsb()` —— 输出 `"键盘已连接(布局:xxx)"` - **实现** `stopUsb()` —— 输出 `"键盘已断开"` - **特有方法**:`void type()` —— 输出 `"键盘输入:Hello World!"` **`USBHub` 类**(实现 Usb,**拓展新设备类型**): - **私有属性**:`portCount`(USB 接口数量,int) - **构造方法**:`USBHub(int portCount)` - **实现** `startUsb()` —— 输出 `"USB 扩展坞已连接(xxx 个接口)"` - **实现** `stopUsb()` —— 输出 `"USB 扩展坞已断开"` - **特有方法**:`void expandPorts()` —— 输出 `"扩展出 xxx 个 USB 接口,可连接更多设备"` **`NoteBook` 类**: - **私有属性**:`brand`(品牌,String)、`ports`(Usb 设备数组,最多 4 个)、`portIndex`(当前已插入设备数) - **构造方法**:`NoteBook(String brand)` —— 初始化品牌,创建大小为 4 的 Usb 数组 - **方法**:`void plugIn(Usb device)` —— 插入 Usb 设备到 `ports` 数组 - **方法**:`void workAll()` —— 遍历 `ports` 数组,对每个设备: 1. 调用 `startUsb()` 2. 使用 `instanceof` 判断设备类型,向下转型调用特有方法 - 如果是 `Mouse` → 调用 `click()` - 如果是 `Keyboard` → 调用 `type()` - 如果是 `USBHub` → 调用 `expandPorts()` 3. 调用 `stopUsb()` ### 测试类要求 **`UsbManagerTest` 类**: 1. 创建 `NoteBook` 对象("ThinkPad") 2. 创建多个 Usb 设备: - `Mouse`("罗技") - `Keyboard`("QWERTY") - `USBHub`(4) - `Mouse`("雷蛇") 3. 将设备依次插入笔记本 4. 调用 `workAll()` 让所有设备工作一轮 ### 预期输出 ``` ========== ThinkPad 笔记本 Usb 设备工作 ========== 鼠标已连接(品牌:罗技) 鼠标单击——嗒 鼠标已断开 键盘已连接(布局:QWERTY) 键盘输入:Hello World! 键盘已断开 USB 扩展坞已连接(4 个接口) 扩展出 4 个 USB 接口,可连接更多设备 USB 扩展坞已断开 鼠标已连接(品牌:雷蛇) 鼠标单击——嗒 鼠标已断开 ``` ### 思考题 1. `NoteBook` 类中的 `workAll()` 方法,参数和返回值的类型设计体现了什么原则? 2. 如果要新增一个 `Printer`(打印机)Usb 设备,需要修改 `NoteBook` 类的代码吗?这符合哪个设计原则? 3. 对照课堂的 `Test02.java`,本练习增加了什么功能?(提示:从单个设备到多个设备 + instanceof 判断) ### 代码填空挑战 请补全 `NoteBook` 类的 `workAll()` 方法: ```java public void workAll() { System.out.println("========== " + brand + " 笔记本 Usb 设备工作 =========="); for (int i = 0; i < portIndex; i++) { Usb device = ports[i]; device.startUsb(); // 请使用 instanceof + 向下转型 调用特有方法 // 你的代码: device.stopUsb(); System.out.println(); } } ``` ## 📋 题目总览 | 序号 | 所属部分 | 核心知识点 | 难度 | |------|---------|-----------|------| | 练习 1 | 随堂 | 多态概念、三前提、父类引用指向子类对象 | ⭐ | | 练习 2 | 随堂 | 多态成员访问特点(编译看左运行看右) | ⭐⭐ | | 练习 3 | 随堂 | 向下转型、instanceof 运算符、调用子类特有方法 | ⭐⭐⭐ | | 练习 4 | 随堂 | 多态判断、多态与普通继承的区别 | ⭐⭐ | | 进阶 1 | 进阶 | 多态数组 + instanceof + 向下转型 + 员工管理综合 | ⭐⭐⭐⭐ | | 进阶 2 | 进阶 | 接口多态 + Usb 设备数组 + instanceof + 开闭原则 | ⭐⭐⭐⭐⭐ |