20260724-笔记.md 19 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.javacourse.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.javaNoteBook.javaMouse.javacourse.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.javacourse.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.javacourse.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.javacourse.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.javacourse.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.javacourse.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.javaStudent.javaTeacher.javacourse.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.javaCat.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.javacourse.internalClass 包)

4.3 成员内部类

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

创建格式

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

访问权限修饰符

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

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.javaTest01.javacourse.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.javacourse.internalClass 包)

五、随堂练习要点

练习方向 涉及知识点 对应文件
多态入门 多态前提、父类引用指向子类对象、成员访问特点 Test01.java(course.polymorphism 包)Dog.java(course.polymorphism 包)
多态中的转型 向上转型、向下转型、instanceof 运算符 Demo02.java(course.polymorphism 包)Test03.java(course.polymorphism 包)
抽象类多态综合 抽象父类 Person、多态参数、向下转型调特有方法 Test03.javaPerson.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 等)
└── 私有内部类设计原则
    ├── 内部类私有化,不被外界直接访问
    └── 外部类提供公共方法对外暴露内部类功能