- 继承(Inheritance)-- 代码重用和扩展(导致子类与父类耦合)。
- 作用:
- 1、表示 is-a 关系。
- 2、支持多态特性。
- 3、代码复用。
1、继承的概述
- 关键字 extends 指示正在构造的新类派生于一个已存在的类。
- 这个已存在的类称为超类(superclass)、基类(base class)、父类(parent class) 。
- 新类称为子类(subclass / child class)、派生类(derived class) 。
- 超类 和 子类 是 Java 程序员最常用的两个术语。
- “超”(super)和 “子”(sub)来源于计算机科学与数学理论中集合语言的术语。
- 所有员工组成的集合包含所有经理组成的集合。
- 所以,可以说,员工集合是经理集合的超集;经理集合是员工集合的子集。
2、继承的模式
- 从继承关系上来讲,继承可以分为两种模式:
- 单继承:表示一个子类只继承一个父类。
- 多继承:表示一个子类可以继承多个父类。

3、继承所需的支持
- 为了实现继承这个特性,编程语言需要提供特殊的语法机制 来支持。
- Java 使用 extends 关键字来实现继承 。
- C++ 使用冒号(class B : public A)。
- Python 使用 paraentheses()。
- Ruby 使用 < 。
- 有些编程语言只支持单继承,不支持多重继承。
- 有些编程语言既支持单继承,又支持多重继承。
4、Java 不支持多继承的原因
原因 | 解释 |
---|
菱形继承问题 | 避免方法/属性冲突,简化继承逻辑。 |
语言设计简洁性 | 减少构造函数、内存布局和类型系统的复杂性。 |
接口的替代性 | 通过接口多实现和默认方法提供灵活性。 |
组合优于继承 | 鼓励更灵活、低耦合的代码复用方式。 |
代码可维护性 | 单一继承层次更清晰,减少调试和维护难度。 |
- 1、避免菱形继承(钻石继承)问题。
- 指一个类同时继承两个父类,而这两个父类又继承自同一个祖先类,导致方法或属性的冲突。
- 会导致调用该方法时的二义性(该执行哪个父类的方法呢?)。
- C++ 通过虚继承(Virtual Inheritance)解决此问题,但会引入额外的复杂性。
class A {
void method() { System.out.println("A"); }
}
class B extends A {
void method() { System.out.println("B"); }
}
class C extends A {
void method() { System.out.println("C"); }
}
class D extends B, C { }

- 2、可以简化语言设计和实现
- 构造函数链的复杂性:
- 多继承需要处理多个父类的构造函数 调用顺序,可能导致初始化逻辑混乱。
- 内存布局复杂性:
- 对象需要包含所有父类的字段,内存布局可能变得复杂,影响性能和维护性。
- 类型系统复杂性:
- 多继承可能导致类型转换和二义性(如:instanceof 判断)。
- 4、组合优于继承(Composition over Inheritance)
- Java 鼓励用组合(将其它类的对象作为成员)而非继承来实现代码复用。更灵活且避免耦合。
class Engine { }
class Wheel { }
class Car {
private Engine engine;
private Wheel[] wheels;
}
- 5、保持代码清晰和可维护性
- 减少二义性:
- 单继承使类层次结构更清晰,开发者可以明确知道方法的来源。
- 简化调试:
5、Java 中隐藏的多继承
package org.rainlotus.materials.javabase.a04_oop.multiExtend;
public class Test1 {
public static void main(String[] args) {
Tom tom = new Tom();
tom.eat();
}
}
interface People{
default void eat(){
System.out.println("人吃饭");
}
}
interface Man{
default void eat(){
System.out.println("男人吃饭");
}
}
interface Boy extends People, Man {
@Override
default void eat(){
System.out.println("小孩子吃饭");
}
}
class Tom implements People, Boy{
}
- 如:代码是编译报错的。
- 类 org.rainlotus.materials.javabase.a04_oop.multiExtend.Jim 从类型 org.rainlotus.materials.javabase.a04_oop.multiExtend.People 和 org.rainlotus.materials.javabase.a04_oop.multiExtend.Man 中继承了 eat() 的不相关默认值
public class Test1 {
public static void main(String[] args) {
Jim jim = new Jim();
jim.eat();
}
}
interface People{
default void eat(){
System.out.println("人吃饭");
}
}
interface Man{
default void eat(){
System.out.println("男人吃饭");
}
}
class Jim implements People, Man {
}
- 解决办法:在 Jim 类中,重写 eat() 方法。
class Jim implements People, Man{
@Override
public void eat() {
People.super.eat();
}
}
6、继承的优缺点
- 优点:
- 继承的本质在于 抽象。
- 为了提高代码的复用性
- 子类拥有父类的所有属性和方法,从而实现代码的复用 。
- 缺点:
- 1、子类和父类高度耦合,修改父类的代码,会直接影响 到子类。
- 2、继承层次过深过复杂,就会导致代码可读性、可维护性变差。
7、阻止继承:final 类 和 方法
- 有时候,我们希望阻止用户定义某个类的子类。不允许被扩展的类被称为 final 类。
- 如果在类定义中使用了 final 修饰符,就表明这个类是 final 类。
- 注意:枚举 和 记录 总是 final 修饰的,它们不允许扩展。
- 在早期的 java 中,有些程序员为了避免动态绑定带来的系统开销而使用 final 关键字。
- 如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理。
- 这个过程称为 内联(inlining)。
- 如:内联调用 e.getName() 将把它替换为访问字段 e.name。
- 这是一项很有意义的改进,CPU 在处理当前指令时,分支会扰乱预取指令的策略。
- 如果 getName 可能在另外一个类中被覆盖,那么编译器就无法知道覆盖代码将会做什么操作。