20260724-多态练习.md 17 KB

2026-07-24 多态(Polymorphism)基础练习(课堂练习用)

说明:以下习题围绕今日授课核心内容——多态(Polymorphism) 展开,涵盖多态的概念与前提、多态成员访问特点、向上转型/向下转型、instanceof 运算符、多态与抽象类/接口的结合。

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


第一部分:随堂练习

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


练习 1:多态入门——Pet 宠物喂食体系

难度:⭐
知识点:多态概念、多态三个前提条件、父类引用指向子类对象

创建一个宠物喂食体系,体验 同一个方法调用,不同子类表现出不同的行为

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() 有什么缺点?
    }
}

预期输出

小狗在啃骨头...
小猫在吃鱼...

思考题

  1. 以上代码体现了多态的哪个前提条件?
  2. 如果 eat() 方法没有被重写,输出会是什么?为什么?
  3. 如果 Pet 类中还有 sleep() 方法但 DogCat 没有重写它,通过 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 对象,分别使用「父类引用指向子类对象」和「子类引用指向子类对象」两种方式,观察成员变量、成员方法、特有方法的访问差异。

模板代码

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

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";

拓展思考

// 以下代码是否是多态?为什么?
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 名员工(各类型混合)

    // 示例数据
    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() 方法:

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 + 开闭原则 ⭐⭐⭐⭐⭐