# JavaSE 基础 — Day10 笔记 > **日期**: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();` | ```java // 父类引用指向子类对象 Animal animal = new Dog(); animal.eat(); animal.sleep(); ``` > 📄 来源:`Test01.java` --- ### 1.3 多态中成员的访问特点 | 成员类型 | 编译时期 | 运行时期 | |----------|---------|---------| | **成员变量** | 看父类 | 看父类(没有多态) | | **成员方法** | 看父类(需存在) | 看子类(重写后执行子类) | **口诀**:编译看左边,运行看右边(针对方法而言)。 ```java // 仅能调用父类中声明的方法 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 接口案例** ```java // 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` 包) ```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) 即"父类的引用指向子类的对象",前面所有示例都属于向上转型。 ```java Person p = new Student(); // ✅ 向上转型 Animal a = new Dog(); // ✅ 向上转型 Usb mouse = new Mouse(); // ✅ 向上转型(接口) ``` #### 1.5.2 向下转型(Downcasting) **概念**:将父类引用 **强制转换** 为子类引用,恢复子类对象的完整形态。 **格式**: ```java 子类类型 对象名 = (子类类型) 父类引用名; ``` **前提**:必须先执行向上转型。 ```java // 1、先向上转型 Person p = new Student(); // 2、再向下转型 Student s = (Student) p; // ✅ 强制转换 ``` **为什么需要向下转型?** - 向上转型后,父类引用**只能调用父类中声明的方法**,无法调用子类的**特有方法** - 若需调用子类特有方法(如 `study()`、`play()`),必须向下转型 ```java 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` 表示否 | ```java // 生活案例:外卖点餐 public void 点外卖(外卖类 wm) { if (wm instanceof 手撕烤鸭类) { 手撕烤鸭类 ssky = (手撕烤鸭类) wm; ssky.准备塑料手套(); } else if (wm instanceof 麻辣烫类) { 麻辣烫类 mlt = (麻辣烫类) wm; mlt.准备筷子(); } } ``` > 📄 来源:`Demo02.java`(`course.polymorphism` 包) --- ### 1.6 综合案例:runPerson() 方法 将多态、`instanceof`、向下转型三者结合使用: ```java 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 抽象类定义 ```java public abstract class Animal { public void eat() { // 普通方法 System.out.println("动物吃饭....."); } public abstract void sleep(); // 抽象方法 } ``` > 📄 来源:`Animal.java`(`course.polymorphism` 包) ### 2.2 子类继承抽象类 ```java 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 ```java // 抽象父类 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 接口定义 ```java public interface Usb { void startUsb(); // 抽象方法(默认 public abstract) void stopUsb(); } ``` > 📄 来源:`Usb.java` ### 3.2 实现 Usb 接口的设备 ```java // 鼠标实现 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 笔记本接入设备——多态参数的应用 ```java // 笔记本——方法参数为 Usb 接口类型 public class NoteBook { public void useUsb(Usb usb) { // 📌 接口类型参数 —— 多态的体现 usb.startUsb(); System.out.println("开始工作....."); usb.stopUsb(); } } ``` > 📄 来源:`NoteBook.java` ### 3.4 测试多态 ```java 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` 就是内部类。 **使用场景**:当一个类的属性不足以描述内部细节时使用。 ```java class 人类 { // 外部类 心脏; // 仅一个属性无法表达心脏的复杂行为 } class 汽车 { // 外部类 发动机; // 发动机应有自己的属性和方法 } ``` > 心脏和发动机都依赖于「人类」和「汽车类」而存在,且它们本身也有自己的属性和方法,所以应该设计为 **内部类**。 **定义格式**: ```java class 外部类名 { 修饰符 class 内部类名 { // 内部类的成员 } } ``` ### 4.2 内部类的特点 | 特点 | 说明 | |------|------| | **① 内部类可直接访问外部类所有成员** | 包括 private 修饰的成员 | | **② 外部类访问内部类成员需创建对象** | 外部类方法中 `new` 内部类对象后再调用 | ```java 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 成员内部类 **成员内部类**:存在于外部类内部,属于外部类的成员,与成员变量、成员方法、代码块在同一层次。 #### 创建格式 ```java 外部类名.内部类名 对象名 = new 外部类().new 内部类(); // 示例: Person.Heart h = new Person().new Heart(); ``` #### 访问权限修饰符 成员内部类可以使用访问权限修饰符(`private`、`public` 等)进行修饰。 ```java public class Person { // 私有化内部类——外界无法直接访问 private class Heart { // ... } // 对外提供公共方法获取内部类对象 public Heart getHeart() { return new Heart(); } } ``` #### 为什么推荐私有化内部类? ```java // ❌ 不合理的做法——Heart 和 Person 无关联 public class Person { Heart heart; } public class Test01 { public static void main(String[] args) { // 可以直接跳过 Person,单独创建 Heart 对象 Heart h = new Heart(); // 不符合现实逻辑 } } ``` ```java // ✅ 合理的做法——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 外部类名.内部类名()` | | 调用静态方法 | `外部类名.内部类名.静态方法名()` | ```java class Outer { static class Inner { // 静态成员内部类 // ... } } // 创建静态内部类对象 Outer.Inner inner = new Outer.Inner(); ``` #### 4.5.2 局部内部类(定义在方法内部) 定义在方法内部的类,**仅在该方法内可见和使用**。 ```java 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 等) └── 私有内部类设计原则 ├── 内部类私有化,不被外界直接访问 └── 外部类提供公共方法对外暴露内部类功能 ```