说明:以下习题围绕今日授课核心内容——多态(Polymorphism) 展开,涵盖多态的概念与前提、多态成员访问特点、向上转型/向下转型、instanceof 运算符、多态与抽象类/接口的结合。
题目分为两部分:随堂练习(多态基础,4 题)和 进阶练习(综合挑战,2 题)。
以下练习围绕多态的基础概念和用法,由浅入深。
难度:⭐
知识点:多态概念、多态三个前提条件、父类引用指向子类对象
创建一个宠物喂食体系,体验 同一个方法调用,不同子类表现出不同的行为。
Pet 类(父类):
void eat() —— 输出 "宠物在吃饭..."Dog 类(继承 Pet):
eat() —— 输出 "小狗在啃骨头..."Cat 类(继承 Pet):
eat() —— 输出 "小猫在吃鱼..."测试类 PetTest 要求:
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() 有什么缺点?
}
}
预期输出:
小狗在啃骨头...
小猫在吃鱼...
思考题:
eat() 方法没有被重写,输出会是什么?为什么?Pet 类中还有 sleep() 方法但 Dog 和 Cat 没有重写它,通过 p1.sleep() 调用会执行哪个类的 sleep()?难度:⭐⭐
知识点:多态中成员变量和成员方法的访问特点、编译期 vs 运行期
通过代码验证多态的成员访问规则。
交通工具 体系:
Vehicle 类(父类):
String name = "交通工具"void run() —— 输出 "交通工具在行驶..."void stop() —— 输出 "交通工具刹车停止"static void info() —— 输出 "Vehicle 静态方法"Car 类(继承 Vehicle):
String name = "小汽车"run() —— 输出 "小汽车在高速行驶..."void openSunroof() —— 输出 "打开天窗~"测试类要求:
编写 VehicleTest 类,创建一个 Car 对象,分别使用「父类引用指向子类对象」和「子类引用指向子类对象」两种方式,观察成员变量、成员方法、特有方法的访问差异。
模板代码:
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() → __________
总结填空:
难度:⭐⭐⭐
知识点:向上转型、向下转型、instanceof 运算符、子类特有方法的调用
场景描述:动物园里有多种动物,管理员需要根据不同的动物执行不同的操作。有些动物会游泳,有些动物会飞翔,这些是特有行为。
Animal 类(父类):
void eat() —— 输出 "动物在吃东西"void sleep() —— 输出 "动物在睡觉"Fish 类(继承 Animal):
eat() —— 输出 "鱼儿在吃饲料"sleep() —— 输出 "鱼儿睁着眼睛睡觉"void swim() —— 输出 "鱼儿在水中游来游去~"Bird 类(继承 Animal):
eat() —— 输出 "小鸟在啄米"sleep() —— 输出 "小鸟在巢穴里睡觉"void fly() —— 输出 "小鸟在天空中飞翔~"请完成 ZooKeeper 类:
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);
}
}
预期输出:
鱼儿在吃饲料
鱼儿睁着眼睛睡觉
鱼儿在水中游来游去~
小鸟在啄米
小鸟在巢穴里睡觉
小鸟在天空中飞翔~
思考题:
keeper.handleAnimal(new Animal()) 会发生什么?instanceof 判断会进入哪个分支?instanceof 判断,直接强制转型 (Fish) animal,当传入 Bird 对象时会怎样?animal.swim() 可以吗?为什么?难度:⭐⭐
知识点:多态的判断、多态与普通继承的区别
判断以下代码片段中,哪些体现了多态,哪些没有,并说明理由。
| 序号 | 代码片段 | 是否多态? | 理由 |
|---|---|---|---|
| 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"; |
拓展思考:
// 以下代码是否是多态?为什么?
public void feed(Pet pet) {
pet.eat();
}
// 调用
feed(new Dog());
feed(new Cat());
参考答案思路:
| 序号 | 是否多态? | 理由 |
|---|---|---|
| 1 | ✅ 是 | 父类引用指向子类对象,调用重写方法 —— 满足多态三前提 |
| 2 | ❌ 否 | 子类引用指向子类对象,没有「父类引用指向子类对象」 |
| 3 | ✅ 是 | 接口引用指向实现类对象,属于接口多态 |
| 4 | ❌ 否 | 没有父类引用,没有方法重写的调用 |
| 5 | ✅ 是 | 先向上转型(多态),再向下转型恢复子类完整形态 |
| 6 | ✅ 是 | String 是 Object 的子类,属于继承多态 |
以下为综合挑战题,融合多态、向上转型/向下转型、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 * hoursWorkedvoid clockIn() —— 输出 "兼职员工 xxx 打卡上班..."(xxx 为姓名)Intern 类(继承 Employee):
allowance(实习津贴,double)Intern(int id, String name, double allowance)calculateSalary() —— 返回 allowance(固定津贴,无其他扣减)void learnSkill() —— 输出 "实习生 xxx 在学习新技能..."(xxx 为姓名)EmployeeManager 类:
创建员工数组:创建一个 Employee[] 数组,包含至少 5 名员工(各类型混合)
// 示例数据
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);
遍历数组输出基本信息:调用每个元素的 showInfo() 方法
遍历数组,按类型执行特有行为:
FullTimeEmployee → 向下转型 → 调用 attendMeeting()PartTimeEmployee → 向下转型 → 调用 clockIn()Intern → 向下转型 → 调用 learnSkill()计算总薪资成本:遍历数组,累加所有员工的 calculateSalary(),输出总薪资
查找薪资最高的员工:遍历数组,找出 calculateSalary() 最大的员工,输出其信息
Employee[] 数组中存储的是不同类型对象,这体现了面向对象哪个特征?instanceof 判断类型,直接向下转型会有什么风险?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)
基于课堂上的 Usb 接口案例,扩展实现一个 Usb 设备管理器。要求:
NoteBook 类可以接入多个 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 数组,对每个设备:
startUsb()instanceof 判断设备类型,向下转型调用特有方法
Mouse → 调用 click()Keyboard → 调用 type()USBHub → 调用 expandPorts()stopUsb()UsbManagerTest 类:
NoteBook 对象("ThinkPad")Mouse("罗技")Keyboard("QWERTY")USBHub(4)Mouse("雷蛇")workAll() 让所有设备工作一轮========== ThinkPad 笔记本 Usb 设备工作 ==========
鼠标已连接(品牌:罗技)
鼠标单击——嗒
鼠标已断开
键盘已连接(布局:QWERTY)
键盘输入:Hello World!
键盘已断开
USB 扩展坞已连接(4 个接口)
扩展出 4 个 USB 接口,可连接更多设备
USB 扩展坞已断开
鼠标已连接(品牌:雷蛇)
鼠标单击——嗒
鼠标已断开
NoteBook 类中的 workAll() 方法,参数和返回值的类型设计体现了什么原则?Printer(打印机)Usb 设备,需要修改 NoteBook 类的代码吗?这符合哪个设计原则?Test02.java,本练习增加了什么功能?(提示:从单个设备到多个设备 + instanceof 判断)请补全 NoteBook 类的 workAll() 方法:
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 + 开闭原则 | ⭐⭐⭐⭐⭐ |