--- title: "01-继承与多态" created: 2025-11-25 tags: - Java --- # 继承与多态 ## 核心概念 继承(Inheritance)和多态(Polymorphism)是面向对象编程的核心特性。继承实现代码复用,多态实现灵活扩展。理解它们的底层机制,是写出优雅OOP代码的基础。 --- ## 一、继承(Inheritance) ### 1.1 基本语法 ```java // 父类(基类/超类) public class Animal { protected String name; public void eat() { System.out.println(name + " is eating"); } } // 子类(派生类) public class Dog extends Animal { public void bark() { System.out.println(name + " is barking"); } } // 使用 Dog dog = new Dog(); dog.name = "Buddy"; dog.eat(); // 继承自Animal dog.bark(); // Dog特有 ``` ### 1.2 Java继承的特点 | 特点 | 说明 | 示例 | | --- | --- | --- | | **单继承** | 一个类只能有一个直接父类 | `class A extends B` | | **多层继承** | 可以有继承链 | A→B→C→Object | | **传递性** | 子类拥有所有祖先的非私有成员 | C可访问A、B的成员 | | **根类Object** | 所有类都隐式继承Object | 默认有toString()等方法 | ### 1.3 继承中的构造函数 ```java class Parent { protected String name; public Parent() { System.out.println("Parent无参构造"); } public Parent(String name) { this.name = name; System.out.println("Parent有参构造"); } } class Child extends Parent { private int age; public Child() { // 隐式调用 super() System.out.println("Child无参构造"); } public Child(String name, int age) { super(name); // 显式调用父类有参构造,必须在第一行 this.age = age; System.out.println("Child有参构造"); } } // 测试 new Child(); // 输出:Parent无参构造 → Child无参构造 new Child("Tom", 20); // 输出:Parent有参构造 → Child有参构造 ``` **构造函数规则:** - 子类构造函数**必须**调用父类构造函数 - 如果不显式调用,编译器自动插入`super()` - 如果父类没有无参构造,子类必须显式调用有参构造 - `super()`必须在构造函数第一行 ### 1.4 super关键字 ```java public class Child extends Parent { private String name; // 与父类同名字段 public Child(String name) { super(name); // 1. 调用父类构造函数 this.name = name; } @Override public void eat() { super.eat(); // 2. 调用父类方法 System.out.println("Child eating"); } public void showName() { System.out.println(this.name); // 子类的name System.out.println(super.name); // 3. 访问父类字段 } } ``` ### 1.5 初始化顺序 ```java class Parent { static { System.out.println("1. Parent静态块"); } { System.out.println("3. Parent实例块"); } Parent() { System.out.println("4. Parent构造函数"); } } class Child extends Parent { static { System.out.println("2. Child静态块"); } { System.out.println("5. Child实例块"); } Child() { System.out.println("6. Child构造函数"); } } new Child(); // 输出顺序:1 → 2 → 3 → 4 → 5 → 6 ``` **初始化顺序:** 1. 父类静态成员/静态块 2. 子类静态成员/静态块 3. 父类实例成员/实例块 4. 父类构造函数 5. 子类实例成员/实例块 6. 子类构造函数 --- ## 二、里氏替换原则(LSP) ### 2.1 定义 > 所有引用基类的地方必须能透明地使用其子类的对象。 —— Barbara Liskov 简单说:**子类对象必须能够替换父类对象,且程序行为不变。** ### 2.2 正确示例 ```java class Rectangle { protected int width; protected int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } // 正确:正方形不应该继承矩形 class Square { private int side; public void setSide(int side) { this.side = side; } public int getArea() { return side * side; } } ``` ### 2.3 违反LSP的示例 ```java // ❌ 错误设计:正方形继承矩形 class Square extends Rectangle { @Override public void setWidth(int width) { this.width = width; this.height = width; // 强制相等 } @Override public void setHeight(int height) { this.width = height; this.height = height; // 强制相等 } } // 问题:使用父类引用时行为不一致 void resize(Rectangle r) { r.setWidth(5); r.setHeight(10); assert r.getArea() == 50; // Rectangle: 50, Square: 100 ❌ } ``` ### 2.4 LSP的约束 | 约束 | 说明 | | --- | --- | | **前置条件不能加强** | 子类方法的输入参数要求不能比父类更严格 | | **后置条件不能削弱** | 子类方法的输出结果不能比父类更宽松 | | **不变式必须保持** | 父类的约束条件子类必须遵守 | | **历史约束** | 子类不能修改父类的状态变化规则 | --- ## 三、多态(Polymorphism) ### 3.1 多态的三个必要条件 ```java // 1. 继承关系 class Animal { public void speak() { System.out.println("Animal"); } } class Dog extends Animal { @Override public void speak() { System.out.println("Woof"); } } class Cat extends Animal { @Override public void speak() { System.out.println("Meow"); } } // 2. 方法重写(见上) // 3. 向上转型(父类引用指向子类对象) Animal a1 = new Dog(); Animal a2 = new Cat(); a1.speak(); // Woof(运行时调用Dog的方法) a2.speak(); // Meow(运行时调用Cat的方法) ``` ### 3.2 编译时类型 vs 运行时类型 ```java Animal animal = new Dog(); // ↑ 编译时类型 ↑ 运行时类型 // 编译时:检查Animal类是否有该方法 // 运行时:调用实际对象(Dog)的方法 animal.speak(); // ✅ Animal有speak(),运行时调用Dog.speak() animal.bark(); // ❌ 编译错误:Animal没有bark()方法 ``` ### 3.3 向上转型与向下转型 ```java // 向上转型(自动,安全) Animal animal = new Dog(); // 向下转型(强制,需要检查) Dog dog = (Dog) animal; // ✅ 实际是Dog对象 Cat cat = (Cat) animal; // ❌ ClassCastException // 安全的向下转型 if (animal instanceof Dog) { Dog d = (Dog) animal; d.bark(); } // JDK 16+ 模式匹配 if (animal instanceof Dog d) { d.bark(); // 直接使用d } ``` ### 3.4 动态绑定(方法调用机制) ```java class Parent { public void method() { System.out.println("Parent"); } } class Child extends Parent { @Override public void method() { System.out.println("Child"); } } Parent p = new Child(); p.method(); // 输出:Child ``` **动态绑定过程:** 1. JVM获取对象的**实际类型**(Child) 2. 在实际类型的方法表中查找方法 3. 如果找到,调用该方法 4. 如果没找到,向父类查找 ### 3.5 方法调用优先级 当存在继承、重写、重载时,方法调用遵循以下优先级: ```java class Parent { public void method(Object obj) { System.out.println("Parent-Object"); } } class Child extends Parent { public void method(Object obj) { System.out.println("Child-Object"); } public void method(String str) { System.out.println("Child-String"); } } Child c = new Child(); c.method("hello"); // Child-String(优先匹配更精确的参数类型) c.method(new Object()); // Child-Object Parent p = new Child(); p.method("hello"); // Child-Object(编译时确定调用method(Object)) ``` **优先级规则:** 1. **精确匹配**:参数类型完全一致 2. **自动类型转换**:基本类型自动提升 3. **自动装箱/拆箱** 4. **可变参数** ### 3.6 静态绑定 vs 动态绑定 | 特性 | 静态绑定 | 动态绑定 | | --- | --- | --- | | 绑定时机 | 编译时 | 运行时 | | 适用方法 | static、private、final、构造方法 | 普通实例方法 | | 依据 | 引用类型(编译时类型) | 对象类型(运行时类型) | ```java class Parent { public static void staticMethod() { System.out.println("Parent static"); } public void instanceMethod() { System.out.println("Parent instance"); } } class Child extends Parent { public static void staticMethod() { System.out.println("Child static"); } @Override public void instanceMethod() { System.out.println("Child instance"); } } Parent p = new Child(); p.staticMethod(); // Parent static(静态绑定,看引用类型) p.instanceMethod(); // Child instance(动态绑定,看对象类型) ``` --- ## 四、字段的继承与隐藏 ### 4.1 字段不存在多态 ```java class Parent { public String name = "Parent"; } class Child extends Parent { public String name = "Child"; // 隐藏父类字段,不是重写 } Parent p = new Child(); System.out.println(p.name); // Parent(字段访问看引用类型) Child c = new Child(); System.out.println(c.name); // Child System.out.println(((Parent)c).name); // Parent ``` ### 4.2 字段隐藏 vs 方法重写 | 特性 | 字段隐藏 | 方法重写 | | --- | --- | --- | | 多态 | 不支持 | 支持 | | 访问依据 | 引用类型 | 对象类型 | | 父类访问 | super.field | super.method() | --- ## 五、高频面试题 ### Q1: Java为什么不支持多继承? **答:** - **菱形继承问题**:如果A继承B和C,B和C都有同名方法,A调用哪个? - **复杂性**:多继承增加语言复杂度和编译器实现难度 - **替代方案**:Java通过接口实现多继承的效果(JDK 8+接口可以有默认方法) ### Q2: 重写和重载的区别? **答:** | 特性 | 重写(Override) | 重载(Overload) | | --- | --- | --- | | 位置 | 子类与父类之间 | 同一个类中 | | 方法名 | 相同 | 相同 | | 参数列表 | 相同 | 必须不同 | | 返回类型 | 相同或协变 | 无要求 | | 多态类型 | 运行时多态 | 编译时多态 | ### Q3: 什么是里氏替换原则? **答:** - 子类对象必须能够替换父类对象 - 程序行为不应该因为替换而改变 - 违反LSP的设计会导致代码脆弱,难以维护 ### Q4: 静态方法能被重写吗? **答:** - 不能。静态方法属于类,不属于对象 - 子类可以定义同名静态方法,但这是**隐藏**,不是重写 - 调用时根据引用类型决定,不存在多态 ### Q5: 构造函数能被重写吗? **答:** - 不能。构造函数不是普通方法,不能被继承 - 子类构造函数必须调用父类构造函数(显式或隐式) - 构造函数名必须与类名相同,子类不可能有父类同名的构造函数 ### Q6: 向上转型和向下转型的区别? **答:** ```java // 向上转型:自动,安全 Animal a = new Dog(); // 向下转型:强制,可能失败 Dog d = (Dog) a; // 需要instanceof检查 ``` ### Q7: 为什么字段不支持多态? **答:** - 字段访问在编译时确定,根据引用类型 - 方法调用在运行时确定,根据对象类型 - 这是Java的设计选择,保证字段访问的效率 --- ## 六、最佳实践 ### ✅ 推荐做法 ```java // 1. 使用@Override注解 @Override public void method() { } // 2. 遵循里氏替换原则 // 子类不应该改变父类的行为契约 // 3. 优先使用组合而非继承 class Car { private Engine engine; // 组合 // 而不是 class Car extends Engine } // 4. 向下转型前检查类型 if (animal instanceof Dog dog) { dog.bark(); } // 5. 使用多态简化代码 List animals = Arrays.asList(new Dog(), new Cat()); for (Animal a : animals) { a.speak(); // 多态调用 } ``` ### ❌ 避免做法 ```java // 1. 滥用继承 class Stack extends ArrayList { } // 错误:Stack不是ArrayList // 2. 违反LSP class Square extends Rectangle { } // 正方形不应该继承矩形 // 3. 不检查就向下转型 Dog d = (Dog) animal; // 危险 // 4. 在构造函数中调用可重写方法 class Parent { Parent() { method(); // 危险:子类还未初始化 } public void method() { } } ``` --- ## 相关链接 - [[02-抽象类与接口|02-抽象类与接口]] - 抽象层次的设计 - [[03-访问权限|03-访问权限]] - 封装与可见性控制 - [[04-重写与重载|04-重写与重载]] - 方法签名详解 --- ## 记忆口诀 ``` 继承单一多层传,构造必须调父先 多态三要:继承、重写、向上转 编译看左运行右,动态绑定是关键 里氏替换要遵守,子类替父行为同 字段隐藏无多态,静态方法看引用 ``` --- ⬅️ [[03-BigDecimal|BigDecimal]] 🏠 [[00-Java|00-Java]] ➡️ [[02-抽象类与接口|抽象类与接口]]