Java super 关键字详解教程
深入理解 Java 中 super 关键字的用法、原理与最佳实践
目录
- 一、super 关键字概述
- 二、super 的三种基本用法
- 三、super 访问父类成员变量
- 四、super 调用父类方法
- 五、super 调用父类构造器
- 六、super 与 this 的对比
- 七、使用 super 的注意事项
- 八、super 在实际开发中的应用
- 九、常见面试题汇总
- 十、总结与最佳实践
一、super 关键字概述
1.1 什么是 super
super 是 Java 中的一个引用变量,指向当前对象的直接父类(超类)。它主要用于在子类中访问父类的成员(属性、方法、构造器)。
1.2 为什么需要 super
在 Java 继承体系中,子类可能会:
- 隐藏父类的成员变量(同名变量)
- 重写父类的方法(同名同参方法)
- 扩展父类的构造器逻辑
此时如果还想访问父类中被隐藏或重写的成员,就需要 super 关键字来"穿透"当前类,找到父类的版本。
1.3 super 的本质
关键点:super 不是对象的引用,而是编译器提供的特殊关键字,用于在编译期确定要访问父类的哪个成员。
二、super 的三种基本用法
2.1 用法总览
| 序号 | 用法 | 语法 | 说明 |
|---|---|---|---|
| 1 | 访问父类成员变量 | super.变量名 |
访问被子类隐藏的父类属性 |
| 2 | 调用父类方法 | super.方法名(参数) |
调用被子类重写的父类方法 |
| 3 | 调用父类构造器 | super(参数) 或 super() |
调用父类的构造器初始化父类部分 |
2.2 思维导图
三、super 访问父类成员变量
3.1 什么是变量隐藏(Field Hiding)
当子类声明了与父类同名的成员变量时,父类的变量并不会被覆盖,而是被"隐藏"。此时通过子类对象访问该变量,默认访问的是子类的版本。
3.2 不使用 super 的问题
class Father {
String name = "父亲";
int age = 50;
}
class Son extends Father {
String name = "儿子"; // 隐藏了父类的 name
int age = 25; // 隐藏了父类的 age
public void print() {
System.out.println(name); // 输出 "儿子"
System.out.println(age); // 输出 25
}
}
3.3 使用 super 访问父类被隐藏的变量
class Father {
String name = "父亲";
int age = 50;
}
class Son extends Father {
String name = "儿子";
int age = 25;
public void print() {
System.out.println("子类 name: " + name); // 儿子
System.out.println("父类 name: " + super.name); // 父亲
System.out.println("子类 age: " + age); // 25
System.out.println("父类 age: " + super.age); // 50
}
}
3.4 内存模型图
3.5 完整示例
class Animal {
String type = "动物";
String name = "通用名";
}
class Dog extends Animal {
String name = "旺财"; // 隐藏父类 name
void showInfo() {
System.out.println("this.name = " + this.name); // 旺财
System.out.println("super.name = " + super.name); // 通用名
System.out.println("super.type = " + super.type); // 动物
}
}
public class TestSuperField {
public static void main(String[] args) {
Dog dog = new Dog();
dog.showInfo();
}
}
输出结果:
this.name = 旺财
super.name = 通用名
super.type = 动物
四、super 调用父类方法
4.1 什么是方法重写(Override)
子类重新实现父类中已有的方法,称为方法重写。重写后,子类对象调用同名方法时,默认执行子类版本。
4.2 为什么需要调用父类被重写的方法
在以下场景中经常需要:
- 功能扩展:在父类原有功能基础上增加新逻辑
- 复用代码:避免重复实现父类已写好的核心逻辑
- 模板方法模式:父类定义流程,子类填充细节
4.3 基本语法
super.方法名(参数列表);
4.4 完整示例
class Vehicle {
public void start() {
System.out.println("交通工具启动:检查基本状态");
}
public void run() {
System.out.println("交通工具行驶中");
}
}
class Car extends Vehicle {
@Override
public void start() {
System.out.println("汽车启动前:检查油量、电瓶");
super.start(); // 调用父类 start 方法
System.out.println("汽车启动完成");
}
@Override
public void run() {
super.run(); // 调用父类 run 方法
System.out.println("汽车以 80km/h 行驶");
}
}
public class TestSuperMethod {
public static void main(String[] args) {
Car car = new Car();
car.start();
System.out.println("---");
car.run();
}
}
输出结果:
汽车启动前:检查油量、电瓶
交通工具启动:检查基本状态
汽车启动完成
---
交通工具行驶中
汽车以 80km/h 行驶
4.5 执行流程图
下面用一张完整的时序图展示 start() 与 run() 两个方法被调用时的完整执行链路。
关键点:
super是在子类方法的执行过程中插入的调用,不是替代- 调用
super.method()时,JVM 会从方法区找到父类版本并执行 - 父类方法执行完毕后,控制权回到子类方法中
super调用之后的语句 - 这种"先扩展、再复用父类、再收尾"的模式是方法重写中最常见的设计
4.6 链式调用(A → B → C)
class A {
void show() {
System.out.println("A.show");
}
}
class B extends A {
@Override
void show() {
super.show(); // 调用 A.show
System.out.println("B.show");
}
}
class C extends B {
@Override
void show() {
super.show(); // 调用 B.show(B.show 内部会再调用 A.show)
System.out.println("C.show");
}
}
public class TestChain {
public static void main(String[] args) {
new C().show();
}
}
输出结果:
A.show
B.show
C.show
五、super 调用父类构造器
5.1 为什么需要 super 调用构造器
在创建子类对象时,必须先初始化父类部分,再初始化子类部分。Java 通过 super 调用父类构造器来完成父类部分的初始化。
5.2 基本规则
| 规则 | 说明 |
|---|---|
| 必须在子类构造器第一行 | 否则编译报错 |
与 this() 互斥 |
二者只能出现一个 |
| 不写也会隐式调用 | 若子类构造器未显式调用,编译器自动添加 super() |
| 必须存在匹配的构造器 | 父类必须有无参构造或显式调用的构造器 |
5.3 语法形式
// 形式一:调用父类无参构造器
super();
// 形式二:调用父类有参构造器
super(参数1, 参数2, ...);
5.4 完整示例
class Person {
private String name;
private int age;
public Person() {
System.out.println("Person 无参构造器");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person 有参构造器: " + name + ", " + age);
}
}
class Student extends Person {
private String studentId;
public Student() {
super(); // 显式调用父类无参构造器
System.out.println("Student 无参构造器");
}
public Student(String name, int age, String studentId) {
super(name, age); // 调用父类有参构造器
this.studentId = studentId;
System.out.println("Student 有参构造器: " + studentId);
}
}
public class TestSuperConstructor {
public static void main(String[] args) {
System.out.println("=== 创建 Student1 ===");
Student s1 = new Student();
System.out.println("\n=== 创建 Student2 ===");
Student s2 = new Student("张三", 20, "S001");
}
}
输出结果:
=== 创建 Student1 ===
Person 无参构造器
Student 无参构造器
=== 创建 Student2 ===
Person 有参构造器: 张三, 20
Student 有参构造器: S001
5.5 隐式调用 super()
class Parent {
public Parent() {
System.out.println("Parent 构造器");
}
}
class Child extends Parent {
public Child() {
// 编译器会自动添加 super();
System.out.println("Child 构造器");
}
}
等价于:
class Child extends Parent {
public Child() {
super(); // 编译器隐式添加
System.out.println("Child 构造器");
}
}
5.6 创建对象的执行流程
5.7 典型错误:父类没有无参构造器
class Parent {
private int id;
public Parent(int id) { // 只有有参构造器,没有无参
this.id = id;
}
}
class Child extends Parent {
public Child() {
// 编译错误!
// 隐式 super() 不存在匹配的父类构造器
System.out.println("Child");
}
}
解决方法:
class Child extends Parent {
public Child() {
super(0); // 显式调用父类有参构造器
System.out.println("Child");
}
}
5.8 this() 与 super() 的互斥
class Animal {
String name;
int age;
public Animal() {
System.out.println("Animal 无参");
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Animal 有参");
}
}
class Dog extends Animal {
String breed;
public Dog() {
this("未知", 0, "土狗"); // 调用本类其他构造器
// 编译错误:this() 和 super() 只能出现一个
// super(); ← 不能同时存在
}
public Dog(String name, int age, String breed) {
super(name, age); // 通过本类构造器间接调用父类
this.breed = breed;
System.out.println("Dog 有参: " + breed);
}
}
图示:
六、super 与 this 的对比
6.1 核心区别
| 对比项 | this |
super |
|---|---|---|
| 含义 | 当前对象的引用 | 父类空间的引用 |
| 访问范围 | 本类成员 | 父类成员 |
| 构造器位置 | 必须放在第一行 | 必须放在第一行 |
| 互斥关系 | 不能与 this() 同存 |
不能与 super() 同存 |
| 能否共存 | 可以一起用:先 super() 再 this.成员 |
可以一起用:先 super() 再 this.成员 |
| 静态方法中 | 可以使用(不推荐) | 不能使用 |
6.2 详细对比表
| 特性 | this | super |
|---|---|---|
| 类型 | 引用变量 | 关键字 |
| 指向 | 当前对象 | 父类对象部分 |
| 调用本类构造器 | this(参数) |
不支持 |
| 调用父类构造器 | 不支持 | super(参数) |
| 访问本类成员 | this.成员 |
不支持 |
| 访问父类成员 | 不直接支持 | super.成员 |
| 是否能用于 static | 可以但不推荐 | 禁止 |
6.3 思维导图对比
6.4 综合示例
class Base {
int x = 10;
Base() {
System.out.println("Base 构造器");
}
Base(int x) {
this.x = x;
System.out.println("Base(int) 构造器");
}
void show() {
System.out.println("Base.show, x = " + x);
}
}
class Sub extends Base {
int x = 20; // 隐藏父类 x
Sub() {
super(); // 调用父类无参构造器
System.out.println("Sub 构造器");
}
Sub(int x) {
super(x); // 调用父类有参构造器,传入的参数会赋给父类 x
System.out.println("Sub(int) 构造器");
}
@Override
void show() {
System.out.println("Sub.show, this.x = " + this.x);
System.out.println("Sub.show, super.x = " + super.x);
}
void test() {
this.show(); // 调用本类 show
super.show(); // 调用父类 show
}
}
public class TestThisSuper {
public static void main(String[] args) {
Sub s1 = new Sub();
s1.test();
System.out.println("---");
Sub s2 = new Sub(100);
s2.test();
}
}
输出结果:
Base 构造器
Sub 构造器
Sub.show, this.x = 20
Sub.show, super.x = 10
Base.show, x = 10
---
Base(int) 构造器
Sub(int) 构造器
Sub.show, this.x = 20
Sub.show, super.x = 100
Base.show, x = 100
七、使用 super 的注意事项
7.1 super 不能用于 static 方法
class Parent {
int value = 10;
}
class Child extends Parent {
int value = 20;
public static void staticMethod() {
// 编译错误!Cannot use super in a static context
// System.out.println(super.value);
}
}
原因:static 方法属于类,不属于任何对象,而 super 必须依赖具体对象才能指向父类部分。
7.2 super() 必须放在构造器第一行
class Parent {
Parent() {
System.out.println("Parent");
}
}
class Child extends Parent {
Child() {
System.out.println("Child 第一行");
super(); // 编译错误!Call to super must be first statement
}
}
正确写法:
Child() {
super(); // 必须在第一行
System.out.println("Child 第一行");
}
7.3 super() 与 this() 不能共存
class Child extends Parent {
Child() {
super(); // 编译错误!两者只能选一个
this("hello"); // 编译错误!
}
}
解决方案:通过 this() 间接调用 super()。
class Child extends Parent {
String data;
Child() {
this("default"); // 合法:this() 在第一行
}
Child(String data) {
super(); // 合法:这里可以写 super()
this.data = data;
}
}
7.4 父类无默认构造器时必须显式调用
class Parent {
Parent(int x) { // 父类只有有参构造器
System.out.println("Parent(int)");
}
}
class Child extends Parent {
Child() {
super(10); // 必须显式调用,否则编译错误
System.out.println("Child");
}
}
7.5 super 不会传递到祖父类构造器
class GrandParent {
GrandParent() {
System.out.println("GrandParent");
}
}
class Parent extends GrandParent {
Parent() {
super(); // 显式调用 GrandParent
System.out.println("Parent");
}
}
class Child extends Parent {
Child() {
super(); // 显式调用 Parent
System.out.println("Child");
}
}
调用链:Child() → super() → Parent() → super() → GrandParent()
7.6 注意事项总结表
| 注意事项 | 是否允许 | 说明 |
|---|---|---|
| static 方法中使用 super | ❌ 不允许 | static 与对象无关 |
| super() 放在第二行 | ❌ 不允许 | 必须第一行 |
| 构造器中同时出现 this() 和 super() | ❌ 不允许 | 互斥 |
| 省略 super() 编译器自动添加 | ✅ 允许 | 仅当父类有无参构造器 |
| 多级继承中 super 不跨级 | ✅ 行为如此 | 每次只调用直接父类 |
八、super 在实际开发中的应用
8.1 应用场景总览
8.2 场景一:实体类的标准写法
class BaseEntity {
private Long id;
private Long createTime;
private Long updateTime;
public BaseEntity() {}
public BaseEntity(Long id, Long createTime, Long updateTime) {
this.id = id;
this.createTime = createTime;
this.updateTime = updateTime;
}
// getter / setter 省略
}
class User extends BaseEntity {
private String username;
private String email;
public User() {
super();
}
public User(Long id, String username, String email) {
super(id, System.currentTimeMillis(), System.currentTimeMillis());
this.username = username;
this.email = email;
}
}
8.3 场景二:方法扩展(常见业务需求)
// 父类提供基础保存逻辑
class BaseService<T> {
public boolean save(T entity) {
System.out.println("基础校验");
System.out.println("通用日志记录");
// 实际保存逻辑
return true;
}
}
// 子类扩展业务校验
class UserService extends BaseService<User> {
@Override
public boolean save(User entity) {
if (entity.getUsername() == null) {
System.out.println("用户名不能为空");
return false;
}
return super.save(entity); // 调用父类保存逻辑
}
}
8.4 场景三:模板方法模式
abstract class DataProcessor {
// 模板方法:定义处理流程
public final void process() {
readData();
parseData();
superHook(); // 钩子方法
saveData();
}
void readData() {
System.out.println("读取数据");
}
abstract void parseData();
void superHook() {
// 默认空实现,子类可选择性覆盖
}
void saveData() {
System.out.println("保存数据");
}
}
class JsonProcessor extends DataProcessor {
@Override
void parseData() {
System.out.println("解析 JSON 数据");
}
@Override
void superHook() {
System.out.println("JSON 特殊预处理");
}
}
8.5 场景四:调用父类构造器实现完整初始化
class Shape {
private String color;
public Shape(String color) {
this.color = color;
System.out.println("Shape 初始化: color=" + color);
}
public String getColor() { return color; }
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color); // 必须先初始化父类
this.radius = radius;
System.out.println("Circle 初始化: radius=" + radius);
}
public double getArea() {
return Math.PI * radius * radius;
}
}
8.6 场景五:多层继承的字段访问
class A {
int value = 1;
}
class B extends A {
int value = 2; // 隐藏 A.value
}
class C extends B {
int value = 3; // 隐藏 B.value
void show() {
System.out.println("this.value = " + this.value); // 3
System.out.println("super.value = " + super.value); // 2(B.value)
// 无法直接通过 super 访问 A.value
}
}
九、常见面试题汇总
9.1 面试题一:super 与 this 的区别
问题:请详细说明 this 和 super 的区别。
参考答案:
| 维度 | this | super |
|---|---|---|
| 概念 | 当前对象引用 | 父类对象引用 |
| 访问 | 本类成员 | 父类成员 |
| 构造器 | this(参数) |
super(参数) |
| 静态方法 | 不推荐使用 | 禁止使用 |
9.2 面试题二:构造器执行顺序
问题:创建子类对象时,构造器的执行顺序是怎样的?
执行顺序:
验证代码:
class Parent {
static {
System.out.println("Parent 静态代码块");
}
{
System.out.println("Parent 实例代码块");
}
Parent() {
System.out.println("Parent 构造器");
}
}
class Child extends Parent {
static {
System.out.println("Child 静态代码块");
}
{
System.out.println("Child 实例代码块");
}
Child() {
super();
System.out.println("Child 构造器");
}
}
public class TestOrder {
public static void main(String[] args) {
new Child();
}
}
输出:
Parent 静态代码块
Child 静态代码块
Parent 实例代码块
Parent 构造器
Child 实例代码块
Child 构造器
9.3 面试题三:super() 必须放在第一行的原因
问题:为什么 super() 必须放在子类构造器的第一行?
答案:
- 保证父类先初始化:Java 要求先完成父类部分的初始化,才能使用父类的成员
- 避免状态不一致:如果允许在父类初始化前访问父类成员,可能导致 NPE 或脏数据
- JVM 设计约束:JVM 在执行
<init>方法时需要明确知道父类构造器的调用点
9.4 面试题四:变量隐藏与方法重写的对比
问题:成员变量的隐藏和方法重写在 super 访问上有什么区别?
答案:
| 特性 | 成员变量 | 方法 |
|---|---|---|
| 父类成员是否被覆盖 | 隐藏(共存) | 重写(替换) |
| 父类版本能否通过对象访问 | 不能(必须 super) | 不能(必须 super) |
| 编译期绑定 | 静态绑定 | 动态绑定(多态) |
示例说明:
class Parent {
int num = 10;
void show() { System.out.println("Parent.show"); }
}
class Child extends Parent {
int num = 20;
@Override
void show() {
System.out.println("Child.show");
}
}
public class TestBinding {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.num); // 10(变量:编译期静态绑定)
p.show(); // Child.show(方法:运行期动态绑定)
}
}
9.5 面试题五:父类构造器抛出异常
问题:父类构造器抛出异常,子类构造器需要如何处理?
答案:
class Parent {
public Parent() throws Exception {
throw new Exception("父类构造器异常");
}
}
class Child extends Parent {
public Child() throws Exception {
super(); // 异常会传播
}
}
子类构造器必须显式声明父类构造器抛出的异常,或在第一行用 try-catch 处理。
9.6 面试题六:super 能否访问父类的 private 成员
问题:能否通过 super 访问父类的 private 成员?
答案:
class Parent {
private int privateField = 10;
public int publicField = 20;
}
class Child extends Parent {
void test() {
// 编译错误!Cannot access private field of super
// System.out.println(super.privateField);
System.out.println(super.publicField); // 合法
}
}
解释:private 成员对子类不可见,super 关键字无法突破访问权限限制,只能访问 public、protected、同包 default 成员。
9.7 面试题七:super 的"穿透性"
问题:super 能否跨越多层继承访问祖父类的成员?
答案:
super 只指向直接父类,不能直接访问祖父类。但可以通过链式调用实现:
class GrandParent {
void hello() { System.out.println("GrandParent"); }
}
class Parent extends GrandParent {
@Override
void hello() {
super.hello(); // 调用 GrandParent.hello
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
void hello() {
super.hello(); // 调用 Parent.hello(内部会再调 GrandParent.hello)
System.out.println("Child");
}
}
十、总结与最佳实践
10.1 知识体系图
10.2 核心要点回顾
- super 是什么:编译期关键字,指向父类,用于访问父类被隐藏或重写的成员
- 三种用法:访问字段、调用方法、调用构造器
- 构造器规则:
super()必须放在第一行,与this()互斥,省略时隐式调用 - 与 this 区别:this 指本类,super 指父类
- static 限制:super 不能在 static 上下文使用
- 访问权限:super 不能突破 private 限制
10.3 最佳实践
| 实践 | 说明 |
|---|---|
| 显式调用 super | 建议显式写 super(),代码更清晰 |
| 父类构造器保留无参 | 便于子类继承和框架反射 |
| 父类成员用 protected | 给子类合理的访问权限 |
| 避免多层 super 链 | 继承层级不超过 3-4 层 |
| 方法重写调用 super | 体现扩展而非完全替换 |
| 业务字段避免同名 | 减少 super 使用的复杂度 |
10.4 速记口诀
super 三用途:字段、方法、构造器
构造第一行:与 this 互不兼容
静态不能用:依赖对象才可行
private 不可见:访问权限要记清
变量是隐藏:方法是重写
多态看方法:变量看类型
10.5 学习路径建议
附录:完整综合案例
// 完整的综合示例
class Animal {
protected String name;
protected int age;
public Animal() {
this.name = "未知";
this.age = 0;
System.out.println("Animal() 无参");
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Animal(name, age) 有参");
}
public void eat() {
System.out.println(name + " 在吃东西");
}
public void sleep() {
System.out.println(name + " 在睡觉");
}
}
class Dog extends Animal {
private String breed;
public Dog() {
super(); // 调用父类无参
this.breed = "土狗";
System.out.println("Dog() 无参");
}
public Dog(String name, int age, String breed) {
super(name, age); // 调用父类有参
this.breed = breed;
System.out.println("Dog(name, age, breed) 有参");
}
@Override
public void eat() {
super.eat(); // 调用父类 eat
System.out.println(breed + " 品种的 " + name + " 啃骨头");
}
public void showInfo() {
System.out.println("name = " + this.name); // 本类
System.out.println("name = " + super.name); // 父类(继承自父类)
System.out.println("breed = " + this.breed); // 本类独有
}
}
public class FinalDemo {
public static void main(String[] args) {
System.out.println("=== 创建 Dog1 ===");
Dog d1 = new Dog();
d1.eat();
d1.showInfo();
System.out.println("\n=== 创建 Dog2 ===");
Dog d2 = new Dog("旺财", 3, "金毛");
d2.eat();
}
}
输出结果:
=== 创建 Dog1 ===
Animal() 无参
Dog() 无参
未知 在吃东西
土狗 品种的 未知 啃骨头
name = 未知
name = 未知
breed = 土狗
=== 创建 Dog2 ===
Animal(name, age) 有参
Dog(name, age, breed) 有参
旺财 在吃东西
金毛 品种的 旺财 啃骨头

浙公网安备 33010602011771号