20260724-多态与接口进阶练习.md 29 KB

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

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

题目分为三部分:随堂练习(多态基础,4 题)、进阶练习(综合挑战,2 题)和 课后作业(接口默认方法/静态方法 + 模板方法,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 名员工(各类型混合)

| 类型 | id | 姓名 | 参数 | |------|----|------|------| | FullTimeEmployee | 1001 | 张三 | 基本工资 8000,绩效 2000 | | PartTimeEmployee | 2001 | 李四 | 时薪 50,工作 80 小时 | | Intern | 3001 | 王五 | 津贴 2000 | | FullTimeEmployee | 1002 | 赵六 | 基本工资 10000,绩效 3000 | | PartTimeEmployee | 2002 | 孙七 | 时薪 45,工作 60 小时 |

  1. 遍历数组输出基本信息:调用每个元素的 showInfo() 方法

  2. 遍历数组,按类型执行特有行为

    • 如果是 FullTimeEmployee → 向下转型 → 调用 attendMeeting()
    • 如果是 PartTimeEmployee → 向下转型 → 调用 clockIn()
    • 如果是 Intern → 向下转型 → 调用 learnSkill()
  3. 计算总薪资成本:遍历数组,累加所有员工的 calculateSalary(),输出总薪资

  4. 查找薪资最高的员工:遍历数组,找出 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();
    }
}

第三部分:课后作业

以下作业围绕接口 JDK8 新特性(默认方法、静态方法)和模板方法设计模式,结合课堂通知系统案例进行扩展。


作业 1:接口默认方法 + 静态方法——消息通知系统扩展

难度:⭐⭐⭐
知识点:接口默认方法(default)、接口静态方法(static)、implements 实现接口、方法重写

需求说明

在课堂 Notifiable 通知系统基础上,增加更多的通知方式。要求使用 接口默认方法 提供通用的日志记录和参数校验功能,使用 接口静态方法 提供工具方法。

接口定义

Notifiable 接口(可直接复用课堂代码,或自行重新定义):

  • 抽象方法void doSend(String recipient, String message)
  • 默认方法default boolean validateParams(...) —— 校验参数
  • 默认方法default void log(String level, String msg) —— 记录日志
  • 默认方法default void send(String recipient, String message) —— 模板方法(固定发送流程)
  • 静态方法static void printNotificationType() —— 输出系统信息

要求实现以下 3 个通知类

SMSNotifier(实现 Notifiable):

  • 重写 doSend() —— 输出 "发送短信给 xxx:内容 -xxx"
  • 不重写 log() —— 使用接口默认实现
  • 不重写 validateParams() —— 使用接口默认实现

WeChatNotifier(实现 Notifiable):

  • 私有属性appId(应用 ID,String)
  • 构造方法WeChatNotifier(String appId)
  • 重写 doSend() —— 输出 "发送微信消息给 xxx(appId: xxx):内容 -xxx"
  • 重写 log() —— 输出 "[微信][xxx]消息:xxx"(添加 [微信] 前缀)

StationNotifier(实现 Notifiable,站内信通知):

  • 私有属性platformName(平台名称,String)
  • 构造方法StationNotifier(String platformName)
  • 重写 doSend() —— 输出 "[xxx] 站内信发送给 xxx:内容 -xxx"
  • 重写 log() —— 输出 "[站内信][xxx]消息:xxx"

测试类要求

NotificationTest

public class NotificationTest {
    public static void main(String[] args) {
        // 1. 调用接口静态方法
        Notifiable.printNotificationType();
        
        // 2. 创建不同通知方式的对象
        Notifiable sms = new SMSNotifier();
        Notifiable wechat = new WeChatNotifier("wx_app_001");
        Notifiable station = new StationNotifier("企业办公平台");
        
        // 3. 发送通知
        System.out.println("--- 短信通知 ---");
        sms.send("13800138000", "您的验证码是 123456");
        
        System.out.println("--- 微信通知 ---");
        wechat.send("user_openid_123", "您有一条新消息");
        
        System.out.println("--- 站内信通知 ---");
        station.send("全体员工", "下午 3 点会议室开会");
        
        // 4. 测试空参数——触发校验失败
        System.out.println("--- 空参数测试 ---");
        sms.send("", "test");
    }
}

预期输出

通知系统 v1.0——支持邮件/短信/微信通知
--- 短信通知 ---
参数校验通过
发送短信给 13800138000:内容 -您的验证码是 123456
[INFO]消息:发送成功
--- 发送流程结束 ---
--- 微信通知 ---
参数校验通过
发送微信消息给 user_openid_123(appId: wx_app_001):内容 -您有一条新消息
[微信][INFO]消息:发送成功
--- 发送流程结束 ---
--- 站内信通知 ---
参数校验通过
[企业办公平台] 站内信发送给 全体员工:内容 -下午 3 点会议室开会
[站内信][INFO]消息:发送成功
--- 发送流程结束 ---
--- 空参数测试 ---
参数校验失败:收件人或消息不能为空

思考题

  1. SMSNotifier 没有重写 log() 方法,调用时执行的是谁的 log()
  2. 接口静态方法 printNotificationType() 能通过实现类对象调用吗?如 sms.printNotificationType() —— 尝试后说说你的发现。
  3. 如果 Notifiable 新增一个默认方法 default void sendWithRetry(...),已有的 3 个实现类需要修改吗?这体现了接口默认方法的什么优势?

作业 2:模板方法设计模式——数据备份系统

难度:⭐⭐⭐⭐
知识点:模板方法设计模式、接口默认方法、抽象方法与默认方法的配合

需求说明

开发一个数据备份系统,所有备份任务都遵循相同的流程,但不同的备份方式(数据库备份、文件备份、云备份)在具体执行步骤上有所不同。

核心流程(模板方法定义):

  1. 校验参数(所有备份相同)—— 检查备份路径/配置是否合法
  2. 连接资源(不同备份方式不同)← 抽象方法
  3. 执行备份(不同备份方式不同)← 抽象方法
  4. 记录日志(所有备份相同)—— 记录备份结果
  5. 关闭资源(不同备份方式不同)← 抽象方法

使用接口 + 默认方法实现模板方法模式

Backupable 接口

public interface Backupable {
    // ===== 抽象方法(实现类必须实现) =====
    void connect();           // 连接资源
    void executeBackup();     // 执行备份
    void close();             // 关闭资源

    // ===== 默认方法(可选重写) =====
    default boolean validateConfig(String path) {
        if (path != null && !path.isEmpty()) {
            System.out.println("配置校验通过:" + path);
            return true;
        }
        System.out.println("配置校验失败:路径不能为空");
        return false;
    }

    default void log(String operation, String status) {
        System.out.println("[备份日志] " + operation + " —— " + status);
    }

    // ===== 模板方法(定义固定流程,使用 default 关键字) =====
    default void backup(String configPath) {
        // 1. 校验配置
        if (!validateConfig(configPath)) return;
        
        // 2. 连接资源
        connect();
        
        // 3. 执行备份
        executeBackup();
        
        // 4. 记录日志
        log("数据备份", "完成");
        
        // 5. 关闭资源
        close();
        
        System.out.println("--- 备份流程结束 ---");
    }

    // ===== 静态方法 =====
    static void printSystemInfo() {
        System.out.println("数据备份系统 v2.0——支持数据库/文件/云备份");
    }
}

要求实现以下 3 个备份类

DatabaseBackup(实现 Backupable):

  • 私有属性dbName(数据库名,String)
  • 构造方法DatabaseBackup(String dbName)
  • 实现 connect() —— 输出 "连接数据库:xxx"
  • 实现 executeBackup() —— 输出 "执行数据库备份:导出表结构和数据..."
  • 实现 close() —— 输出 "关闭数据库连接"
  • 重写 log() —— 输出 "[数据库备份] xxx —— xxx"(覆盖默认日志格式)

FileBackup(实现 Backupable):

  • 私有属性sourceDir(源目录,String)
  • 构造方法FileBackup(String sourceDir)
  • 实现 connect() —— 输出 "扫描本地目录:xxx"
  • 实现 executeBackup() —— 输出 "执行文件备份:压缩文件并复制到备份目录..."
  • 实现 close() —— 输出 "释放文件流资源"
  • 注意:不重写 log(),使用接口默认实现

CloudBackup(实现 Backupable):

  • 私有属性cloudProvider(云服务商,String,如 "阿里云OSS" / "腾讯云COS")
  • 构造方法CloudBackup(String cloudProvider)
  • 实现 connect() —— 输出 "连接云存储服务:xxx"
  • 实现 executeBackup() —— 输出 "执行云备份:上传数据到云端..."(上传完成后追加输出 "上传完成!"
  • 实现 close() —— 输出 "断开云存储连接"
  • 重写 validateConfig() —— 在父类校验基础上,额外检查 cloudProvider 是否以 "云" 结尾,如果不是则输出 "云服务商名称不合法" 并返回 false

测试类要求

BackupTest

  1. 调用接口静态方法输出系统信息
  2. 创建三种备份对象,分别调用 backup() 方法
  3. 测试配置校验失败的情况(传入空路径)

    public class BackupTest {
    public static void main(String[] args) {
        // 1. 输出系统信息
        Backupable.printSystemInfo();
            
        // 2. 数据库备份
        System.out.println("--- 数据库备份 ---");
        Backupable dbBackup = new DatabaseBackup("ERP_System");
        dbBackup.backup("/data/backup/db");
            
        // 3. 文件备份
        System.out.println("--- 文件备份 ---");
        Backupable fileBackup = new FileBackup("/home/user/documents");
        fileBackup.backup("/data/backup/files");
            
        // 4. 云备份
        System.out.println("--- 云备份 ---");
        Backupable cloudBackup = new CloudBackup("阿里云OSS");
        cloudBackup.backup("/data/backup/cloud");
            
        // 5. 测试校验失败
        System.out.println("--- 配置校验失败测试 ---");
        dbBackup.backup("");
    }
    }
    

预期输出

数据备份系统 v2.0——支持数据库/文件/云备份
--- 数据库备份 ---
配置校验通过:/data/backup/db
连接数据库:ERP_System
执行数据库备份:导出表结构和数据...
[数据库备份] 数据备份 —— 完成
关闭数据库连接
--- 备份流程结束 ---
--- 文件备份 ---
配置校验通过:/data/backup/files
扫描本地目录:/home/user/documents
执行文件备份:压缩文件并复制到备份目录...
[备份日志] 数据备份 —— 完成
释放文件流资源
--- 备份流程结束 ---
--- 云备份 ---
配置校验通过:/data/backup/cloud
连接云存储服务:阿里云OSS
执行云备份:上传数据到云端...
上传完成!
[备份日志] 数据备份 —— 完成
断开云存储连接
--- 备份流程结束 ---
--- 配置校验失败测试 ---
配置校验失败:路径不能为空

对比思考

对比维度 课堂 Notifiable + send() 本作业 Backupable + backup()
模板方法 send() backup()
抽象方法数量 1 个(doSend 3 个(connectexecuteBackupclose
默认方法数量 3 个(validateParamslogsend 3 个(validateConfiglogbackup
静态方法 printNotificationType() printSystemInfo()

思考题

  1. 模板方法 backup()default 修饰而不是 abstract,为什么这样设计?
  2. 如果希望子类 不能重写 模板方法 backup(),应该用什么关键字修饰?(提示:回忆 abstract 中的 final 关键字)
  3. 观察 DatabaseBackup 重写了 log(),而 FileBackup 没有重写 —— 这体现了接口默认方法的什么优势?
  4. 本作业中 Backupable 是一个接口,如果改用抽象类实现,会有什么不同?(提示:单继承 vs 多实现)

📋 题目总览

序号 所属部分 核心知识点 难度
练习 1 随堂 多态概念、三前提、父类引用指向子类对象
练习 2 随堂 多态成员访问特点(编译看左运行看右) ⭐⭐
练习 3 随堂 向下转型、instanceof 运算符、调用子类特有方法 ⭐⭐⭐
练习 4 随堂 多态判断、多态与普通继承的区别 ⭐⭐
进阶 1 进阶 多态数组 + instanceof + 向下转型 + 员工管理综合 ⭐⭐⭐⭐
进阶 2 进阶 接口多态 + Usb 设备数组 + instanceof + 开闭原则 ⭐⭐⭐⭐⭐
作业 1 课后 接口默认方法/静态方法、通知系统扩展 ⭐⭐⭐
作业 2 课后 模板方法设计模式(接口默认方法实现)、数据备份系统 ⭐⭐⭐⭐