继承
- 类继承
-
- 类继承的概念
- 子类到底继承了父类的哪些成员?
-
- 继承关系图示
- [private 方法](#private 方法)
- [final 方法(会被继承但不能重写)](#final 方法(会被继承但不能重写))
- 构造方法
- 包级私有方法(跨包时)
- [关于 static 方法的补充说明](#关于 static 方法的补充说明)
- 访问权限与其他修饰符的组合效果表
- 构造方法的继承规则
- 接口继承
- 笔试面试题
类继承
类继承的概念
继承的本质 :子类继承父类的所有非私有成员(属性和方法),但能否访问取决于访问权限修饰符。
java
class Parent {
// 父类的成员
}
public class Child extends Parent {
// 子类的成员
}
子类到底继承了父类的哪些成员?
继承关系图示
┌─────────────────────────────────────┐
│ 父类 (Parent) │
├─────────────────────────────────────┤
│ ───────────────────────────────── │
│ - private 方法/属性 │ ← 不被继承,子类完全不可见
│ ~ default 方法/属性 (同包) │ ← 被继承,同包时可访问
│ # protected 方法/属性 │ ← 被继承,子类可访问/重写
│ + public 方法/属性 │ ← 被继承,子类可访问/重写
│ ───────────────────────────────── │
│ 【特殊成员】 │
│ $ static 方法 │ ← 被继承,但被隐藏(不是重写)
│ ◇ final 方法 │ ← 被继承,但不能重写
│ ▣ 构造方法 Parent() │ ← 不被继承,需super()调用
└────────────────┬────────────────────┘
│
│ extends
▼
┌─────────────────────────────────────┐
│ 子类 (Child) │
├─────────────────────────────────────┤
│ 【继承的成员】 │
│ ✓ public/protected 成员 │ ← 可直接使用
│ ✓ default 成员 (同包时) │ ← 受包限制
│ ✓ static 方法 (但被隐藏) │ ← 建议用类名调用
│ ✓ final 方法 (不可修改) │ ← 只能原样使用
│ ───────────────────────────────── │
│ 【新增成员】 │
│ + 扩展属性 │
│ + 新方法 │
│ ⤻ 重写的方法 (@Override) │ ← 改写父类行为
│ $ static 隐藏方法 │ ← 隐藏父类static方法
└─────────────────────────────────────┘
═════════════ 构造顺序 ═════════════
① 父类静态块/属性初始化
↓
② 子类静态块/属性初始化
↓
③ 父类实例属性初始化
↓
④ 父类构造方法执行
↓
⑤ 子类实例属性初始化
↓
⑥ 子类构造方法执行
📌总结一下不会被继承的方法:
private方法- 跨包时的包级私有方法(默认访问权限)
- 构造方法
而 final 方法和 static 方法实际上是被继承的,只是在重写方面有限制。
private 方法
java
public class Parent {
private void privateMethod() {
System.out.println("父类私有方法");
}
}
public class Child extends Parent {
// 无法继承 privateMethod,完全不可见
public void test() {
// privateMethod(); // 编译错误
}
}
final 方法(会被继承但不能重写)
严格来说,final 方法是会被继承的,但不能被子类重写(override):
java
public class Parent {
public final void finalMethod() {
System.out.println("final方法");
}
}
public class Child extends Parent {
// 可以调用 finalMethod()
// 但不能重写:
// public void finalMethod() { } // 编译错误
}
构造方法
构造方法不会被继承,子类必须通过 super() 调用父类构造方法:
java
public class Parent {
public Parent(String name) {
// 有参构造
}
}
public class Child extends Parent {
public Child(String name) {
super(name); // 必须显式调用
}
}
包级私有方法(跨包时)
如果父类和子类不在同一个包中,父类的默认访问权限(无修饰符)方法不会被继承:
java
// 包 com.parent
package com.parent;
public class Parent {
void packagePrivateMethod() { } // 默认访问权限
}
// 包 com.child
package com.child;
import com.parent.Parent;
public class Child extends Parent {
// 无法访问 packagePrivateMethod()
}
关于 static 方法的补充说明
static方法确实会被继承,子类可以通过类名或实例调用- 但
static方法不支持重写(override) ,只支持隐藏(hide) - 如果子类定义了相同的
static方法,父类的方法被隐藏,调用取决于引用类型
java
Parent p = new Child();
p.staticMethod(); // 调用 Parent 的 static 方法,不是 Child 的
访问权限与其他修饰符的组合效果表
Java 提供了四种访问权限修饰符,控制成员的可见范围:
| 访问权限 | 本类 | 同包 | 子类(不同包) | 其他类 |
|---|---|---|---|---|
private |
✓ | ✗ | ✗ | ✗ |
default |
✓ | ✓ | ✗ | ✗ |
protected |
✓ | ✓ | ✓ | ✗ |
public |
✓ | ✓ | ✓ | ✓ |
访问权限 + static
| 组合 | 是否被继承 | 子类访问方式 | 特殊说明 |
|---|---|---|---|
public static |
✅ 是 | 子类名.成员 或 父类名.成员 |
完全可见,可被隐藏 |
protected static |
✅ 是 | 子类名.成员 或 父类名.成员 |
子类及同包可见 |
static (default) |
⚠️ 有条件 | 同包:子类名.成员 跨包:❌ 不可访问 |
包级私有 |
private static |
❌ 否 | 完全不可访问 | 仅父类内部可见 |
访问权限 + final(方法)
| 组合 | 是否被继承 | 能否重写 | 特殊说明 |
|---|---|---|---|
public final |
✅ 是 | ❌ 不能 | 所有子类都只能原样使用 |
protected final |
✅ 是 | ❌ 不能 | 子类及同包可见但不能改 |
final (default) |
⚠️ 有条件 | ❌ 不能 | 同包可继承但不能重写 |
private final |
❌ 否 | - | 实际等同于private方法 |
访问权限 + final(属性)
| 组合 | 是否被继承 | 能否修改 | 特殊说明 |
|---|---|---|---|
public final |
✅ 是 | ❌ 不能 | 全局常量 |
protected final |
✅ 是 | ❌ 不能 | 子类可读不可改 |
final (default) |
⚠️ 有条件 | ❌ 不能 | 同包可读不可改 |
private final |
❌ 否 | - | 仅父类内部使用 |
访问权限 + abstract
| 组合 | 是否被继承 | 必须重写? | 特殊说明 |
|---|---|---|---|
public abstract |
✅ 是 | ✅ 必须 | 最常见形式 |
protected abstract |
✅ 是 | ✅ 必须 | 子类必须实现 |
abstract (default) |
⚠️ 有条件 | ✅ 必须 | 同包必须实现 |
private abstract |
❌ 不允许 | - | 编译错误! 矛盾组合 |
访问权限 + synchronized
| 组合 | 是否被继承 | 锁对象 | 特殊说明 |
|---|---|---|---|
public synchronized |
✅ 是 | 实例对象(this) | 可被重写,锁行为不变 |
protected synchronized |
✅ 是 | 实例对象(this) | 同上 |
synchronized (default) |
⚠️ 有条件 | 实例对象(this) | 同包可继承 |
private synchronized |
❌ 否 | 实例对象(this) | 仅父类内部 |
访问权限 + native
| 组合 | 是否被继承 | 能否重写 | 特殊说明 |
|---|---|---|---|
public native |
✅ 是 | ✅ 能 | 可被Java方法重写 |
protected native |
✅ 是 | ✅ 能 | 同上 |
native (default) |
⚠️ 有条件 | ✅ 能 | 同包可继承 |
private native |
❌ 否 | - | 仅父类内部 |
访问权限 + strictfp
| 组合 | 是否被继承 | 精度规则 | 特殊说明 |
|---|---|---|---|
public strictfp |
✅ 是 | 严格浮点 | 被重写的方法继承精度规则 |
protected strictfp |
✅ 是 | 严格浮点 | 同上 |
strictfp (default) |
⚠️ 有条件 | 严格浮点 | 同包可继承 |
private strictfp |
❌ 否 | 严格浮点 | 仅父类内部 |
📌 关键规则总结
- 访问权限优先 :
private成员永远不被继承,无论其他修饰符 abstract与private冲突:不能组合使用final与继承:可继承但不可修改/重写static特殊:可继承但表现为"隐藏"而非"重写"- default 访问权限:继承受包限制
构造方法的继承规则
父类的构造方法不会被子类继承 ,但子类必须在自己的构造方法中调用父类的构造方法,确保"先父类后子类"的构造顺序。
根据父类情况决定调用方式
| 父类构造方法情况 | 子类调用要求 | 示例 |
|---|---|---|
| 有无参构造 | 可以隐式调用,也可以显式调用 | Child() { } 或 Child() { super(参数); } |
| 只有有参构造 | 必须显式调用父类有参构造 | Child() { super(参数); } |
规则一:隐式调用父类无参构造
如果父类存在无参构造,且子类构造方法中没有显式调用super(...),系统会自动调用父类的无参构造。
java
class Parent {
Parent() { // 父类有无参构造
System.out.println("父类无参构造");
}
}
class Child extends Parent {
Child() {
// 隐式调用 super()
System.out.println("子类无参构造");
}
}
输出结果:
父类无参构造
子类无参构造
规则二:显式调用父类有参构造
如果父类只定义了有参构造 (没有无参构造),子类构造方法必须显式调用 父类的有参构造,且super(参数)必须放在第一行。
java
// 父类:只定义了有参构造
class Parent {
int a;
Parent(int a) { // 有参构造
this.a = a;
System.out.println("父类有参构造");
}
// 此时没有无参构造方法
}
// ❌ 错误写法:编译报错
class Child1 extends Parent {
Child1() {
// 隐式调用 super(),但父类没有无参构造
System.out.println("子类构造");
}
}
// ✅ 正确写法:显式调用父类有参构造
class Child2 extends Parent {
Child2(int a) {
super(a); // 必须显式调用父类的有参构造
System.out.println("子类构造");
}
}
// ✅ 也可以这样:子类无参构造调用本类其他构造,最终调用父类有参构造
class Child3 extends Parent {
Child3() {
this(10); // 调用本类的有参构造
}
Child3(int a) {
super(a); // 最终调用父类有参构造
System.out.println("子类构造");
}
}
java
// 父类:只定义了无参和有参构造
class Parent {
Parent() {
System.out.println("父类无参构造被调用");
}
Parent(int a) {
System.out.println("父类有参构造被调用,a = " + a);
}
}
class Child extends Parent {
// 情况1:隐式调用父类无参构造
Child() {
System.out.println("子类无参构造");
}
// 情况2:显式调用父类无参构造
Child(String name) {
super(); // 显式调用父类无参构造
System.out.println("子类有参构造:" + name);
}
// 情况3:也可以调用父类有参构造
Child(int a) {
super(a); // 调用父类有参构造
System.out.println("子类有参构造,a = " + a);
}
}
public class Test {
public static void main(String[] args) {
System.out.println("=== 创建Child() ===");
Child c1 = new Child();
System.out.println("\n=== 创建Child(\"hello\") ===");
Child c2 = new Child("hello");
System.out.println("\n=== 创建Child(100) ===");
Child c3 = new Child(100);
}
}
三条关键原则
-
super(参数)必须在第一行显式调用父类构造方法的语句必须放在子类构造方法的首行
-
必须直接或间接调用父类构造
子类构造方法执行前,必须完成父类构造方法的调用
-
构造顺序不可改变
永远是:父类构造方法先执行 → 子类构造方法后执行
-
一个子类构造方法只能调用一次父类构造方法
-
可以选择调用父类的无参构造或有参构造
接口继承
补充:接口继承
笔试面试题
例题1:继承构造方法调用错误
java
class Parent {
String name;
// 父类只有带参构造方法
public Parent(String x) {
name = x;
}
}
public class SubClass extends Parent {
public static void main(String[] args) {
SubClass obj = new SubClass(); // 这里会报编译错误
System.out.println(obj.name);
}
}
错误要点总结
- 父类
Parent:只定义了带参构造Parent(String x),没有无参构造 - 子类
SubClass:没有定义任何构造方法,编译器自动添加SubClass() { super(); } - 冲突 :自动添加的
super()试图调用父类的无参构造,但父类没有 - 结果:编译失败