20260724-笔记.md 19 KB

20260724 课堂笔记 — 多态与内部类

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


一、多态(Polymorphism)

1.1 什么是多态

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

例如:

  • 学生 张三 是 Student 类型
  • 若 Student extends Person,则张三同时具备 Student 和 Person 两种形态
  • 若 Person extends Animal,则张三具备 Student、Person、Animal 三种形态

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


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.java、Dog.java(course.polymorphism 包)


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.java、NoteBook.java、Mouse.java(course.polymorphism 包)

// 测试多态
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(course.polymorphism 包)

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(course.polymorphism 包)


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(course.polymorphism 包)

执行流程分析(以传入 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(course.polymorphism 包)

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(course.polymorphism 包)

  • 子类必须重写父类的所有抽象方法(除非子类也是抽象类)
  • 子类可以重写父类的普通方法(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.java、Student.java、Teacher.java(course.polymorphism 包)


三、接口多态案例(USB接口 + 笔记本)

3.1 接口定义

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

📄 来源:Usb.java

3.2 实现 Usb 接口的设备

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

// Cat 也实现 Usb 接口(演示用:任何类都可以实现接口)
public class Cat implements Usb {
    @Override
    public void startUsb() { }
    @Override
    public void stopUsb() { }
}

📄 来源:Mouse.java、Cat.java

3.3 笔记本接入设备——多态参数的应用

// 笔记本——方法参数为 Usb 接口类型
public class NoteBook {
    public void useUsb(Usb usb) {   // 📌 接口类型参数 —— 多态的体现
        usb.startUsb();
        System.out.println("开始工作.....");
        usb.stopUsb();
    }
}

📄 来源:NoteBook.java

3.4 测试多态

public class Test02 {
    public static void main(String[] args) {
        NoteBook noteBook = new NoteBook();

        Usb mouse = new Mouse();        // ✅ 接口引用指向实现类对象
        noteBook.useUsb(mouse);         // 传入鼠标

        Usb cat = new Cat();            // ✅ 猫也实现了 Usb
        noteBook.useUsb(cat);           // 传入猫 USB
    }
}

📄 来源:Test02.java(course.polymorphism 包)

关键点:NoteBook.useUsb(Usb usb) 的参数类型是 接口,任何实现 Usb 接口的类都可以传入,无需修改笔记本的代码即可接入新设备——这就是 多态提高扩展性 的典型应用。


四、内部类(Inner Class)

4.1 什么是内部类

概念:在一个类中定义另一个类。例如,在 A 类的内部定义了 B 类,B 就是内部类。

使用场景:当一个类的属性不足以描述内部细节时使用。

class 人类 {       // 外部类
    心脏;          // 仅一个属性无法表达心脏的复杂行为
}

class 汽车 {       // 外部类
    发动机;        // 发动机应有自己的属性和方法
}

心脏和发动机都依赖于「人类」和「汽车类」而存在,且它们本身也有自己的属性和方法,所以应该设计为 内部类。

定义格式:

class 外部类名 {
    修饰符 class 内部类名 {
        // 内部类的成员
    }
}

4.2 内部类的特点

特点 说明
① 内部类可直接访问外部类所有成员 包括 private 修饰的成员
② 外部类访问内部类成员需创建对象 外部类方法中 new 内部类对象后再调用
public class Person {
    private String name;

    // 内部类
    public class Heart {
        public void jump() {
            // 内部类直接访问外部类的私有成员
            System.out.println(name + "的心脏跳动....");
        }
    }

    // 外部类方法——必须创建内部类对象才能访问
    public void method() {
        Heart h = new Heart();
        h.jump();
    }
}

📄 来源:Demo01.java(course.internalClass 包)

4.3 成员内部类

成员内部类:存在于外部类内部,属于外部类的成员,与成员变量、成员方法、代码块在同一层次。

创建格式

外部类名.内部类名 对象名 = new 外部类().new 内部类();
// 示例:
Person.Heart h = new Person().new Heart();

访问权限修饰符

成员内部类可以使用访问权限修饰符(private、public 等)进行修饰。

public class Person {
    // 私有化内部类——外界无法直接访问
    private class Heart {
        // ...
    }

    // 对外提供公共方法获取内部类对象
    public Heart getHeart() {
        return new Heart();
    }
}

为什么推荐私有化内部类?

// ❌ 不合理的做法——Heart 和 Person 无关联
public class Person {
    Heart heart;
}

public class Test01 {
    public static void main(String[] args) {
        // 可以直接跳过 Person,单独创建 Heart 对象
        Heart h = new Heart();   // 不符合现实逻辑
    }
}
// ✅ 合理的做法——Heart 作为 Person 的私有内部类
public class Person {
    private class Heart {           // 私有内部类
        public void jump() {
            System.out.println("心脏跳动...");
        }
    }
    public Heart getHeart() {       // 对外提供方法
        return new Heart();
    }
}

public class Test01 {
    public static void main(String[] args) {
        Person person = new Person();
        // 必须通过 Person 对象才能获取 Heart
        // person.getHeart();  // 这才是合理的调用链
    }
}

📄 来源:Demo01.java、Test01.java(course.internalClass 包)

设计原则:内部类的设计目的就是 不想让外界直接访问,仅限外部类自己使用。所以内部类应该私有化,并通过外部类的公共方法对外开放。

4.4 内部类与多态的对比

对比维度 多态 内部类
解决什么问题 同一行为在不同子类中有不同实现 类的内部细节需要独立描述
关系 父类/接口 ← 子类/实现类 外部类 → 内部类(强依赖)
访问特点 编译看左边,运行看右边 内部类可直接访问外部类私有成员

4.5 更多内部类类型

4.5.1 静态成员内部类(static 修饰)

用 static 修饰的内部类,属于外部类本身而非外部类的实例。

访问格式:

场景 格式
创建对象 外部类名.内部类名 对象名 = new 外部类名.内部类名()
调用静态方法 外部类名.内部类名.静态方法名()
class Outer {
    static class Inner {   // 静态成员内部类
        // ...
    }
}

// 创建静态内部类对象
Outer.Inner inner = new Outer.Inner();

4.5.2 局部内部类(定义在方法内部)

定义在方法内部的类,仅在该方法内可见和使用。

class Outer {
    public void method() {
        class Inner {               // 局部内部类
            public void innerMethod() {
                // 内部类的方法
            }
        }
        // 必须在方法内创建对象才能使用
        Inner inner = new Inner();
        inner.innerMethod();
    }
}

特点:

  • 外界无法直接访问局部内部类
  • 只能在定义它的方法内部创建对象并调用
  • 可以直接访问外部类的成员,也可以访问所在方法的局部变量

使用频率:局部内部类和静态成员内部类使用较少,使用最多的是 成员内部类 和 匿名内部类(匿名内部类后续课程讲解)。 📄 来源:Demo01.java(course.internalClass 包)

五、随堂练习要点

练习方向 涉及知识点 对应文件
多态入门 多态前提、父类引用指向子类对象、成员访问特点 Test01.java(course.polymorphism 包)、Dog.java(course.polymorphism 包)
多态中的转型 向上转型、向下转型、instanceof 运算符 Demo02.java(course.polymorphism 包)、Test03.java(course.polymorphism 包)
抽象类多态综合 抽象父类 Person、多态参数、向下转型调特有方法 Test03.java、Person.java
接口多态 Usb 接口、笔记本接入多设备 Test02.java(course.polymorphism 包)、NoteBook.java(course.polymorphism 包)
内部类入门 内部类概念、成员内部类、私有内部类设计 包 course.internalClass

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

  1. 多态的实现原理——Java 中多态是通过动态绑定(后期绑定)实现的,JVM 在运行时根据对象的实际类型调用对应的方法。
  2. 多态与继承的关系——多态是基于继承或接口实现的。
  3. 多态的优势——提高代码的可扩展性和可维护性,符合开闭原则(对扩展开放,对修改关闭)。
  4. 向下转型的风险——向下转型必须配合 instanceof 判断,否则可能抛出 ClassCastException(类型转换异常)。
  5. instanceof 的用法——常用于向下转型前的安全判断,避免强制类型转换异常。
  6. 内部类的使用场景——当一个类的内部细节(如心脏之于人、发动机之于汽车)需要独立描述时,应设计为内部类。
  7. 内部类的访问规则——内部类可直接访问外部类的所有成员(含 private);外部类必须创建内部类对象才能访问其成员。
  8. 私有内部类的设计原则——内部类应私有化,并通过外部类的公共方法对外暴露,确保外部类对内部类的完全控制。

七、今日知识图谱

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

内部类 ⭐(今日新增)
├── 概念:类中定义类(心脏之于人、发动机之于汽车)
├── 特点
│   ├── 内部类可直接访问外部类所有成员(含 private)
│   └── 外部类需创建内部类对象才能访问其成员
├── 成员内部类
│   ├── 创建格式:外部类名.内部类名 对象名 = new 外部类().new 内部类();
│   └── 可用访问权限修饰符修饰(private 等)
└── 私有内部类设计原则
    ├── 内部类私有化,不被外界直接访问
    └── 外部类提供公共方法对外暴露内部类功能