20260724-笔记.md 15 KB

JavaSE 基础 — Day10 笔记

日期:2026-07-24
项目c260724
作者:WanJL


一、多态(Polymorphism)

1.1 什么是多态

面向对象三大特征之一(封装、继承、多态)。
同一个对象,在不同时刻表现出来的不同形态。

例如:

  • 学生 张三Student 类型
  • Student extends Person,则张三同时具备 StudentPerson 两种形态
  • Person extends Animal,则张三具备 StudentPersonAnimal 三种形态

核心理解:一个对象可以拥有多种类型身份,通过父类/接口的引用指向子类/实现类的对象。


1.2 多态的前提条件

条件 说明
① 继承或实现关系 子类 extends 父类,或实现类 implements 接口
② 方法重写 子类/实现类必须重写父类/接口的方法
③ 父类引用指向子类对象 Animal a = new Dog();
// 父类引用指向子类对象
Animal animal = new Dog();
animal.eat();
animal.sleep();

📄 来源:Test01.java


1.3 多态中成员的访问特点

成员类型 编译时期 运行时期
成员变量 看父类 看父类(没有多态)
成员方法 看父类(需存在) 看子类(重写后执行子类)

口诀:编译看左边,运行看右边(针对方法而言)。

// 仅能调用父类中声明的方法
Animal animal = new Dog();
animal.eat();   // ✅ 编译看父类有 eat(),运行执行 Dog 的 eat()
animal.sleep(); // ✅ 编译看父类有 sleep()(抽象),运行执行 Dog 的 sleep()
// animal.wangWang(); // ❌ 编译报错,父类没有 wangWang()

📄 来源:Test01.javaDog.java


1.4 多态的好处——提高代码扩展性

多态让代码对扩展开放,对修改关闭(开闭原则)。

典型应用:笔记本 USB 接口案例

// Usb 接口
public interface Usb {
    void startUsb();
    void stopUsb();
}

// 笔记本类——接收任意 Usb 实现类
public class NoteBook {
    public void useUsb(Usb usb) {   // 参数类型为 Usb 接口
        usb.startUsb();
        System.out.println("开始工作.....");
        usb.stopUsb();
    }
}

// 鼠标实现 Usb
public class Mouse implements Usb {
    public void startUsb() { System.out.println("USB鼠标开启"); }
    public void stopUsb()  { System.out.println("USB鼠标关闭"); }
}

📄 来源:Usb.javaNoteBook.javaMouse.java

// 测试多态
NoteBook noteBook = new NoteBook();
Usb mouse = new Mouse();
noteBook.useUsb(mouse);   // ✅ 传入鼠标

Usb cat = new Cat();      // ✅ 猫也能实现 Usb(示例演示)
noteBook.useUsb(cat);     // ✅ 传入猫 USB

📄 来源:Test02.java

关键点NoteBook.useUsb(Usb usb) 的参数是接口类型,任何实现 Usb 接口的类都可以传入,无需修改笔记本代码即可扩展新设备。


1.5 多态中的转型

多态涉及两种类型转换:

1.5.1 向上转型(Upcasting)

即"父类的引用指向子类的对象",前面所有示例都属于向上转型。

Person p = new Student();   // ✅ 向上转型
Animal a = new Dog();       // ✅ 向上转型
Usb mouse = new Mouse();    // ✅ 向上转型(接口)

1.5.2 向下转型(Downcasting)

概念:将父类引用 强制转换 为子类引用,恢复子类对象的完整形态。

格式

子类类型 对象名 = (子类类型) 父类引用名;

前提:必须先执行向上转型。

// 1、先向上转型
Person p = new Student();
// 2、再向下转型
Student s = (Student) p;   // ✅ 强制转换

为什么需要向下转型?

  • 向上转型后,父类引用只能调用父类中声明的方法,无法调用子类的特有方法
  • 若需调用子类特有方法(如 study()play()),必须向下转型

    Person p = new Student();
    p.eat();    // ✅ 父类中有 eat()
    p.sleep();  // ✅ 父类中有 sleep()
    // p.study(); // ❌ 编译报错!父类中没有 study()
    
    Student s = (Student) p;   // 向下转型
    s.study();  // ✅ 转型后可以调用子类特有方法
    

📄 来源:Demo02.java

1.5.3 instanceof 运算符——类型判断

问题:向下转型前,如何确定父类引用指向的是哪个子类对象?直接强转可能引发 ClassCastException

解决:使用 instanceof 运算符进行类型判断。

格式 说明
对象名 instanceof 类型 判断左边对象是否属于右边类型
返回 boolean true 表示是,false 表示否
// 生活案例:外卖点餐
public void 点外卖(外卖类 wm) {
    if (wm instanceof 手撕烤鸭类) {
        手撕烤鸭类 ssky = (手撕烤鸭类) wm;
        ssky.准备塑料手套();
    } else if (wm instanceof 麻辣烫类) {
        麻辣烫类 mlt = (麻辣烫类) wm;
        mlt.准备筷子();
    }
}

📄 来源:Demo02.java


1.6 综合案例:runPerson() 方法

将多态、instanceof、向下转型三者结合使用:

public class Test03 {
    // 参数为抽象父类类型——多态的体现
    public void runPerson(Person p) {
        p.eat();    // ✅ 编译看Person,运行看子类

        // 判断 p 指向的具体子类类型
        if (p instanceof Student) {
            Student s = (Student) p;   // 向下转型
            s.study();                 // 调用子类特有方法
        } else if (p instanceof Teacher) {
            Teacher t = (Teacher) p;   // 向下转型
            t.play();                  // 调用子类特有方法
        }

        p.sleep();  // ✅ 编译看Person,运行看子类
    }

    public static void main(String[] args) {
        Test03 t = new Test03();
        t.runPerson(new Student());   // 向上转型传入 Student
        t.runPerson(new Teacher());   // 向上转型传入 Teacher
    }
}

📄 来源:Test03.java

执行流程分析(以传入 new Student() 为例):

runPerson(new Student())
  ─→ p.eat()               → 输出 "学生吃饭..."
  ─→ p instanceof Student  → true → (Student)p → s.study() → 输出 "学生学习..."
  ─→ p.sleep()             → 输出 "学生睡觉..."

二、抽象类回顾与多态结合

2.1 抽象类定义

public abstract class Animal {
    public void eat() {              // 普通方法
        System.out.println("动物吃饭.....");
    }
    public abstract void sleep();    // 抽象方法
}

📄 来源:Animal.java

2.2 子类继承抽象类

public class Dog extends Animal {
    @Override
    public void sleep() {
        System.out.println("小狗睡觉....");
    }
    @Override
    public void eat() {
        System.out.println("小狗吃饭....");
    }
    public void wangWang() {
        System.out.println("小狗汪汪....");
    }
}

📄 来源:Dog.java

  • 子类必须重写父类的所有抽象方法(除非子类也是抽象类)
  • 子类可以重写父类的普通方法(eat()
  • 子类可以扩展自己的特有方法(wangWang()

2.3 另一组示例:Person → Student / Teacher

// 抽象父类
public abstract class Person {
    public abstract void eat();
    public abstract void sleep();
}

// 子类 Student
public class Student extends Person {
    @Override
    public void eat()  { System.out.println("学生吃饭..."); }
    @Override
    public void sleep(){ System.out.println("学生睡觉..."); }
    public void study(){ System.out.println("学生学习..."); }  // 特有方法
}

// 子类 Teacher
public class Teacher extends Person {
    @Override
    public void eat()  { System.out.println("教师吃饭..."); }
    @Override
    public void sleep(){ System.out.println("教师睡觉..."); }
    public void play() { System.out.println("教师玩....爽...."); }  // 特有方法
}

📄 来源:Person.javaStudent.javaTeacher.java


三、接口多态与接口新特性(JDK8+)

3.1 接口定义

public interface Usb {
    void startUsb();   // 抽象方法(默认 public abstract)
    void stopUsb();
}

📄 来源:Usb.java

3.2 接口默认方法(JDK 8)

在接口中定义带方法体的方法,使用 default 关键字。实现类可以选择重写或不重写。

public interface Notifiable {
    // 抽象方法——实现类必须重写
    void doSend(String recipient, String message);

    // 默认方法——校验参数(实现类可选重写)
    default boolean validateParams(String recipient, String message) {
        if (recipient != null && message != null && !recipient.isEmpty() && !message.isEmpty()) {
            System.out.println("参数校验通过");
            return true;
        }
        System.out.println("参数校验失败:收件人或消息不能为空");
        return false;
    }

    // 默认方法——记录日志(实现类可选重写)
    default void log(String level, String msg) {
        System.out.println("[" + level + "]消息:" + msg);
    }
}

📄 来源:Notifiable.java

3.3 接口静态方法(JDK 8)

使用 static 关键字定义,通过接口名直接调用。

public interface Notifiable {
    static void printNotificationType() {
        System.out.println("通知系统 v1.0——支持邮件/短信/微信通知");
    }
}

// 调用方式
Notifiable.printNotificationType();

📄 来源:Notifiable.javaTestNotification.java


四、模板方法设计模式(基于接口默认方法)

在接口中定义一个 默认方法 作为模板,规定固定的算法流程,让实现类只需关注核心业务逻辑。

public interface Notifiable {
    // 模板方法——定义固定的发送流程
    default void send(String recipient, String message) {
        // 1、校验参数
        boolean boo = validateParams(recipient, message);
        if (boo) {
            // 2、执行发送(调用抽象方法,由实现类完成具体逻辑)
            doSend(recipient, message);
            // 3、记录日志
            log("INFO", "发送成功");
            System.out.println("--- 发送流程结束 ---");
        }
    }
}

📄 来源:Notifiable.java

流程示意图

send()  →  validateParams()  →  doSend()  →  log()  →  完成
(模板)     (默认方法-校验)      (抽象方法)    (默认方法)

五、课后作业:通知系统(Notifiable 体系)

5.1 接口定义

使用 JDK8 新特性定义通知接口:

  • 抽象方法 doSend()——实现类实现不同的发送逻辑
  • 默认方法 validateParams()——参数校验
  • 默认方法 log()——日志记录
  • 默认方法 send()——模板方法,组合流程
  • 静态方法 printNotificationType()——输出系统信息

5.2 实现类

public class EmailNotifier implements Notifiable {
    @Override
    public void doSend(String recipient, String message) {
        System.out.println("发送邮件给 " + recipient + ":内容 -" + message);
    }
    // 可选重写默认方法 log()
    @Override
    public void log(String level, String msg) {
        System.out.println("[邮件][" + level + "]消息:" + msg);
    }
}

📄 来源:EmailNotifier.java

5.3 测试类

public class TestNotification {
    public static void main(String[] args) {
        // 1、调用接口静态方法
        Notifiable.printNotificationType();
        // 2、创建实现类对象
        EmailNotifier en = new EmailNotifier();
        // 3、正常发送
        en.send("admin@163.com", "系统升级通知");
        // 4、参数为空——触发校验失败
        en.send("", "测试");
    }
}

📄 来源:TestNotification.java


六、随堂练习要点

练习方向 涉及知识点 对应文件
多态入门 多态前提、父类引用指向子类对象、成员访问特点 Test01.javaDog.java
多态中的转型 向上转型、向下转型、instanceof 运算符 Demo02.javaTest03.java
抽象类多态综合 抽象父类 Person、多态参数、向下转型调特有方法 Test03.javaPerson.java
接口多态 Usb 接口、笔记本接入多设备 Test02.javaNoteBook.java
通知系统(作业) 接口默认方法、静态方法、模板方法设计模式 作业包 homework

七、拓展阅读 & 常见面试题

  1. 多态的实现原理——Java 中多态是通过动态绑定(后期绑定)实现的,JVM 在运行时根据对象的实际类型调用对应的方法。
  2. 多态与继承的关系——多态是基于继承或接口实现的。
  3. 多态的优势——提高代码的可扩展性和可维护性,符合开闭原则(对扩展开放,对修改关闭)。
  4. 向下转型的风险——向下转型必须配合 instanceof 判断,否则可能抛出 ClassCastException(类型转换异常)。
  5. instanceof 的用法——常用于向下转型前的安全判断,避免强制类型转换异常。
  6. 接口默认方法的用途——在不破坏已有实现类的前提下向接口添加新方法;实现模板方法模式。
  7. 接口静态方法的用途——提供工具方法,接口即命名空间,无需额外工具类。
  8. 模板方法设计模式——定义一个操作中的算法骨架(模板方法),将一些步骤延迟到子类/实现类中实现。

八、今日知识图谱

面向对象三大特征
├── 封装(private / getter / setter)
├── 继承(extends / super / @Override)
└── 多态 ⭐(今日重点)
    ├── 前提:继承/实现 + 重写 + 父类引用指向子类对象
    ├── 成员访问:编译看左边,运行看右边
    ├── 多态中的转型
    │   ├── 向上转型(Upcasting)
    │   │   └── 父类引用指向子类对象:Person p = new Student();
    │   ├── 向下转型(Downcasting)
    │   │   ├── 格式:子类类型 对象名 = (子类类型) 父类引用;
    │   │   ├── 前提:必须先向上转型
    │   │   └── 用途:调用子类特有方法
    │   └── instanceof 运算符
    │       └── 格式:对象名 instanceof 类型(返回 boolean)
    ├── 多态的好处:提高扩展性(开闭原则)
    │   └── 案例:NoteBook.useUsb(Usb usb)
    └── 接口多态 + JDK8 新特性
        ├── 默认方法(default)—— 模板方法设计模式
        └── 静态方法(static)—— 接口名直接调用