一、引言
在 Java 面向对象编程中,继承是实现代码复用与扩展的基础,而方法重载(Overload)和方法重写(Override)则是构建多态特性的两大核心手段。这三者紧密相连,共同构成了 Java 面向对象设计的基石,也是面试中高频考察、日常开发中极易混淆的知识点。
很多开发者能说出 “重载是同一个类中方法名相同参数不同,重写是子类重写父类方法”,但却说不清:
- 继承如何为多态提供了基础?
- 重载和重写在编译期与运行期的执行机制有何本质区别?
- 为什么静态方法不能被重写,只能被隐藏?
- 实际编码中,如何避免写出不符合规范的代码(如误以为 “返回值不同也能重载”,或重写时随意修改访问权限)?
本文将从继承的本质出发,系统梳理继承、方法重载与重写的语法规则、执行机制和设计场景,帮你不仅 “知其然”,更 “知其所以然”,真正吃透 Java 多态的核心。
二、继承
2.1 定义
继承是 Java 面向对象三大特性(封装、继承、多态)之一,核心是让子类(Subclass)复用父类(Superclass)的代码和特性,同时可扩展自定义逻辑。语法上通过extends关键字实现
三、字段
3.1 继承
子类继承父类字段的核心规则是访问权限控制,static/final等修饰符不影响 “继承性”,只影响 “可修改性 / 共享性”。
| 父类字段访问权限 | 子类是否继承 | 关键说明 |
|---|---|---|
| public | ✅ 是 | 子类可直接访问、修改(除非字段是 final) |
| protected | ✅ 是 | 子类(同包 / 不同包)均可访问,不同包的非子类不可访问 |
| default(缺省) | ✅ 是 | 仅同包子类可访问,不同包子类无法访问 |
| private | ❌ 否 | 私有字段仅父类自身可见,子类无法直接继承 / 访问(可通过父类的 public/protected getter/setter 间接操作) |
3.2 访问
static字段:子类继承后,本质是共享父类的静态字段(通过子类名.静态字段访问时,实际指向父类的静态内存空间);final字段:子类继承后可访问,但无法修改(final 约束 “不可赋值”)。- 普通字段(非 static、非 final):子类继承后可直接访问(受访问权限限制),支持自由修改其值,每个子类实例都拥有独立的普通字段内存空间,与其他实例互不影响。
- 被隐藏的字段(子类重定义父类同名字段):子类实例中同时存在父类字段和子类同名字段,直接访问时优先使用子类自己的字段;若要访问父类被隐藏的字段,必须通过
super.字段名显式调用,两者内存独立,互不覆盖。 - 未被隐藏的字段(子类未重定义):子类直接继承父类字段,访问时直接指向父类字段的内存空间,无额外遮蔽,语义清晰,推荐使用。
- 被隐藏的字段(子类重定义父类同名字段):子类实例中同时存在父类字段和子类同名字段,直接访问时优先使用子类自己的字段;若要访问父类被隐藏的字段,必须通过
3.3 隐藏
字段隐藏(Field Hiding)是指子类定义了与父类同名的字段(无论类型、访问权限、修饰符是否一致),导致子类中直接访问该字段时优先指向子类自身定义的版本,父类的同名字段并未被覆盖 / 删除,只是被 “遮蔽”—— 两者在内存中独立存在,是 Java 继承中极易被误解的核心概念。
字段只有 “隐藏”,没有 “重写”,这是和方法最本质的区别。
3.4 无多态
字段访问的规则是 “看引用类型,不看实际对象类型”
// 父类引用指向子类实例(模拟多态场景) Parent p = new Child(); // 仍访问父类字段(无多态) System.out.println(p.info); // 输出:父类原始字段Java 设计字段时选择 “编译时绑定”,而非方法的 “运行时绑定”,核心原因:
- 性能考量:字段访问是直接的内存地址读取,编译时绑定能减少运行时开销(方法重写需要通过方法表动态查找,有性能损耗);
- 语义安全:字段代表对象的 “状态”,若支持运行时绑定,会导致父类定义的字段语义被子类随意篡改,破坏封装性;
- 逻辑一致性:字段是 “数据”,方法是 “行为”—— 多态的核心是 “行为的动态变化”,而非 “数据的动态替换”。
四、内部类
4.1 继承
| 父类内部类类型 | 子类是否继承 | 关键说明 |
|---|---|---|
| public/protected 静态内部类 | ✅ 是 | 子类可通过子类名.静态内部类直接访问,无需父类实例 |
| public/protected 非静态内部类 | ✅ 是 | 子类需先创建父类(子类)实例,再通过实例.new 非静态内部类访问 |
| private 内部类 | ❌ 否 | 仅父类自身可见,子类无法访问 |
4.2 隐藏
子类无法 “重写(Override)” 父类的内部类(无论是静态还是非静态),只能定义 “同名的新内部类”—— 本质是「内部类的隐藏(Hiding)」(所以父类的内部类被final修饰也行),而非重写。
无继承关系:子类同名内部类和父类内部类是两个完全独立的类,彼此无继承 / 重写关系
编译时绑定:访问子类.InnerClass时,编译器直接指向子类的内部类,与父类无关
访问父类内部类的唯一方式:显式指定父类名.InnerClass(静态)或父类实例.new InnerClass()(非静态)
4.3 访问
- 访问父类「静态内部类」
未被隐藏(子类无同名静态内部类)直接用
父类名.静态内部类名或简化为静态内部类名(子类继承后可直接用)。已被隐藏(子类定义了同名静态内部类)必须用
父类名.静态内部类名(不能简化),绕过子类的同名隐藏
- 访问父类「非静态内部类」
- 未隐藏:可通过
this.new 内部类名简化访问; - 已隐藏:必须通过
super.new 内部类名或父类实例.new 父类.内部类名访问
- 未隐藏:可通过
4.4 无多态
内部类(无论静态 / 非静态)完全不支持多态,所有访问都是「编译时绑定」—— 访问结果只看代码中写的 “类型声明”,和 “实际对象类型” 无关。
静态内部类是 “类级成员”,完全不存在多态的可能 —— 哪怕试图用 “父类内部类引用指向子类内部类实例”,编译器直接报错(类型不兼容),因为两者是完全独立的类,无继承关系。
五、构造方法
5.1 无法继承
Java 中,子类永远无法继承父类的构造方法 —— 这是继承规则中最核心的特例之一。构造方法的作用是 “初始化当前类的实例”,属于类的 “专属初始化逻辑”,仅能由当前类调用,无法被子类继承复用。
很多开发者误以为 “子类能调用父类构造方法” 就是 “继承”,但调用 ≠ 继承:调用是复用父类的初始化逻辑,继承是子类拥有父类的方法(可直接重写 / 调用),两者本质不同。
- 语义冲突:构造方法的方法名必须和类名完全一致(如
Parent()属于Parent类,Child()属于Child类),子类无法继承一个 “和自己类名不同” 的构造方法; - 初始化安全:构造方法负责初始化类的私有字段、核心资源,若允许继承,子类可能绕过父类的初始化逻辑,破坏对象的完整性;
- 设计初衷:构造方法是 “创建实例的入口”,每个类需自定义自己的初始化逻辑,继承会导致子类实例初始化失控。
5.2 构造方法的调用
子类虽不能继承父类构造方法,但可通过super()关键字调用父类构造方法,完成父类部分的初始化
- 若子类构造方法中未显式写
super(),编译器会自动在子类构造方法的第一行添加super(),调用父类的无参构造 - 若父类没有无参构造(仅定义了有参构造),子类必须显式调用父类的有参构造(
super(参数)),否则编译报错super()必须是子类构造方法的第一行代码;- 子类构造方法中,
super()和this()(调用子类其他构造方法)不能同时出现; - 若父类只有私有构造方法(
private),则该类无法被继承(子类无法调用父类私有构造) - 子类构造方法最多只能调用一次父类构造方法
5.3 无多态
多态的核心是 “运行时动态绑定”—— 父类引用指向子类实例时,执行子类重写的方法逻辑。但构造方法从设计上就彻底排除了多态的可能:
- 构造方法不能被继承,子类无法重写父类构造方法(无重写则无多态的基础);
- 构造方法的调用是编译时绑定,仅由 “new 关键字后的类名” 决定,与引用类型无关;
- 构造方法的唯一作用是 “初始化当前类实例”,无需也不能动态切换逻辑。
多态的核心是 “方法的动态执行”,而非 “引用的赋值”
Parent p = new Child();中只是把 “子类实例的内存地址” 赋值给 “父类类型的引用变量”,这是多态的语法前提,但不是多态本身;
六、方法
6.1 继承
| 父类方法访问权限 | 能否继承 | 关键说明 | 示例 |
|---|---|---|---|
public(公有) | ✅ 能 | 子类(同包 / 不同包)均可继承、直接调用、重写(final 方法除外) | public void test() {}→ 子类可直接test()调用 |
protected(受保护) | ✅ 能 | 子类(同包 / 不同包)均可继承;不同包的非子类无法访问 | protected void doLogic() {}→ 不同包子类可继承调用 |
default(缺省 / 包私有) | ✅ 能 | 仅同包子类可继承;不同包子类无法继承 / 访问 | void print() {}→ 同包子类可调用,不同包不行 |
| 父类方法修饰符 | 能否继承 | 继承后约束 |
|---|---|---|
static(静态) | ✅ 能 | 子类可继承 / 调用,但不能重写(子类定义同名静态方法属于 “隐藏”,而非重写) |
final(最终) | ✅ 能 | 子类可继承 / 调用,但不能重写(编译报错) |
abstract(抽象) | ✅ 能 | 子类必须重写(除非子类也是抽象类),否则编译报错 |
static final | ✅ 能 | 子类可继承 / 调用,既不能重写,也不能隐藏后修改逻辑(推荐用于工具类方法) |
6.2 隐藏
子类定义了与父类同名的静态方法,导致子类中直接调用该方法时优先指向子类自身的版本,父类的同名方法并未被覆盖,只是被 “遮蔽”—— 这是 Java 继承中与 “方法重写(Override)” 极易混淆的概念,且仅针对static方法 /private方法生效。
- 只有静态方法 /会发生 “隐藏”;
- 非静态的实例方法只会发生 “重写”,不会隐藏;
- 方法隐藏是编译时绑定,无多态特性,与方法重写的 “运行时绑定” 本质不同。
- private 方法不会被继承,子类根本看不到这个父类 private 方法;子类写同名同参 private 方法,只是子类自己新增的独立方法,语法上既不是重写,也不是隐藏
- final无法被重写,直接报错
6.3 方法重写和方法重载
| 对比维度 | 方法重写(Override) | 方法重载(Overload) |
|---|---|---|
| 核心定义 | 子类覆盖父类中方法名 + 参数列表完全相同的方法,替换其实现逻辑 | 同一个类中定义方法名相同、参数列表不同的多个方法,适配不同参数场景 |
| 发生范围 | 父子类之间(继承关系) | 同一个类内部(子类也可对父类方法重载) |
| 方法名 | 必须完全相同 | 必须完全相同 |
| 参数列表 | 必须完全相同(个数 / 类型 / 顺序均一致) | 必须不同(个数 / 类型 / 顺序任意一个不同即可) |
| 返回值 | 需兼容(Java5 + 支持协变:子类返回值是父类的子类型),不能随意修改 | 无要求(仅返回值不同≠重载) |
| 访问权限 | 子类方法权限不能比父类更严格(如父类 public,子类不能 private) | 无要求(可任意修改) |
| 异常抛出 | 子类抛出的异常不能比父类更宽泛(可更少 / 更具体) | 无要求(可任意修改) |
final影响 | 父类 final 方法不能被重写 | final 方法可以被重载 |
static影响 | 静态方法不能被重写(子类静态方法只是隐藏父类方法) | 静态方法可以被重载 |
| 核心目的 | 子类定制 / 扩展父类的方法逻辑,体现多态 | 简化调用,一个方法名适配多种参数场景 |
| 方法调用逻辑 | 运行时绑定(多态,看对象实际类型) | 编译时绑定(看调用时的参数类型 / 个数) |
| 注解使用 | 推荐加@Override注解(编译器校验规则) | 无专属注解 |
| 示例 | 父类public void say()→ 子类public void say() | 类内add(int a)→add(int a, int b)→add(double a) |
6.4 多态
- 多态:同一个行为(方法调用),作用在不同对象上,表现出不同的结果。
- 关键逻辑:父类引用指向子类对象时,调用重写方法,执行的是子类的实现(而不是父类的)—— 这就是重写带来的多态效果。
6.5 访问
| 场景(调用前提) | 访问目标 | 具体操作方法(核心) |
|---|---|---|
| 子类内部,方法已被重写 | 父类方法 | 写super.方法名(参数) |
| 子类内部,方法未被重写 | 父类方法 | 直接写方法名(参数)(继承) |
| 子类内部,任意情况 | 子类方法 | 直接写方法名(参数) |
| 外部类,子类对象调用 | 父类方法 | 1. 子类写中转方法(内部用 super),外部调中转方法;2. 新建父类对象调用 |
| 外部类,子类对象调用 | 子类方法 | 子类对象.方法名 (参数) |
| 外部类,父类引用指向子类对象 | 父类方法 | 新建纯父类对象.方法名 (参数) |
| 外部类,父类引用指向子类对象 | 子类方法 | 父类引用.方法名 (参数)(多态) |
| 任意场景,静态方法 | 父类方法 | 父类名.静态方法名 (参数) |
| 任意场景,静态方法 | 子类方法 | 子类名.静态方法名 (参数) |
| 任意场景,父类 private 方法 | 父类方法 | 调用父类的公有方法间接访问 |
七、问题
7.1 返回值
- 同一个类中:仅返回值不同 → 编译报错(不算重载)
- 父子类之间:仅返回值不同 → 不构成重写(大概率编译报错)
- 返回值不满足协变 → 编译报错(不构成重写)
- 返回值满足协变 → 合法重写
7.2 访问权限
- 同一个类中:仅访问权限不同 → 编译报错(不算重载)
- 父子类之间:仅访问权限不同 → 可能编译报错
- 子类重写方法的访问权限不能比父类更严格,子类比父类严格,编译错误
- 子类方法的权限和父类一致,或更宽松,这是合法的重写,但 “仅改权限” 本身不会让方法变成 “新方法”,只是调整了方法的访问范围
7.3 异常
- 同一个类中:仅异常声明不同 → 编译报错(不算重载)
- 父子类之间:仅异常声明不同 → 不构成重写(大概率编译报错)
- 子类重写方法抛出的受检异常,不能比父类方法声明的异常范围更广(不能抛出父类未声明的新受检异常),否则编译报错(不构成重写)
- 子类重写方法抛出的受检异常,与父类相同,或为父类异常的子类,或不抛出受检异常 → 合法重写
- 运行时异常(
RuntimeException及其子类)不受此限制,子类可自由抛出