super 关键字
本节定位:
super是子类与父类之间的桥梁。它完成三个核心任务:调用父类构造方法、访问被子类隐藏的父类字段、调用被子类重写的父类方法。掌握super与this的区别,是理解 Java 对象初始化全流程的关键。
super 的三重角色
一、super() 调用父类构造方法
语法与规则
public class 子类名 extends 父类名 {
public 子类名(参数列表) {
super(参数); // 必须位于首行
// 子类特有的初始化...
}
}
| 规则 | 说明 |
|---|---|
| 位置 | 必须位于子类构造方法的第一行 |
| 不写 super() | 编译器默认插入 super()(调用父类无参构造) |
| 父类无无参构造 | 子类必须显式调用 super(参数),否则编译错误 |
| this() 与 super() | 不能同时出现在构造方法首行,只能二选一 |
| 多层继承 | super() 只调用直接父类,构造器链向上递归 |
飞翔科技场景:三层继承构造链
架构师白歌为了演示构造器链,设计了一个三层继承场景:
// 顶层:Person(人)
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person 构造: " + name + ", " + age);
}
}
// 中层:Employee(员工)
public class Employee extends Person {
private String employeeId;
private double baseSalary;
public Employee(String name, int age, String employeeId, double baseSalary) {
super(name, age); // 必须显式调用,因为 Person 无无参构造
this.employeeId = employeeId;
this.baseSalary = baseSalary;
System.out.println("Employee 构造: " + employeeId + ", " + baseSalary);
}
}
// 底层:Manager(经理)
public class Manager extends Employee {
private double bonus;
public Manager(String name, int age, String employeeId, double baseSalary, double bonus) {
super(name, age, employeeId, baseSalary);
this.bonus = bonus;
System.out.println("Manager 构造: bonus=" + bonus);
}
}
// 测试
public class SuperConstructorTest {
public static void main(String[] args) {
System.out.println("=== 创建 Manager 对象 ===");
Manager m = new Manager("大翔", 35, "E001", 50000, 200000);
}
}
运行输出:
=== 创建 Manager 对象 ===
Person 构造: 大翔, 35
Employee 构造: E001, 50000.0
Manager 构造: bonus=200000.0
构造器链执行顺序 Mermaid 图
孔蓝点评:理解构造器链的顺序至关重要。当你在父类构造方法中看到某个字段的值为 null 或默认值时,回想这个执行顺序——很可能是子类还来不及初始化。
二、super.成员变量:访问被子类隐藏的父类字段
场景:子类与父类有同名字段
public class Employee {
public String department = "技术部"; // 父类字段
}
public class Manager extends Employee {
public String department = "管理层"; // 子类字段,隐藏了父类同名字段
public void showDepartment() {
System.out.println("this.department = " + this.department); // 管理层
System.out.println("super.department = " + super.department); // 技术部
}
}
public class SuperFieldTest {
public static void main(String[] args) {
Manager m = new Manager();
m.showDepartment();
}
}
运行输出:
this.department = 管理层
super.department = 技术部
原理:字段隐藏 vs 方法重写
| 特性 | 字段 | 方法 |
|---|---|---|
| 子类定义同名成员 | 隐藏(Hiding) | 重写(Override) |
| 访问行为 | 取决于引用类型(编译时绑定) | 取决于实际对象类型(运行时绑定) |
super 访问 | 访问父类被隐藏的字段 | 调用父类被重写的方法 |
| 推荐实践 | 避免子类与父类同名字段 | 鼓励重写实现多态 |
白歌警示:子类中定义与父类同名字段是极其危险的做法。尽量让字段保持
private,只通过方法暴露行为。如果确实需要共享字段,使用protected并明确注释。
三、super.方法:调用被子类重写的父类方法
常见场景:子类扩展父类方法而非完全替换
public class Employee {
public String work() {
return "员工正在工作中";
}
}
public class Manager extends Employee {
@Override
public String work() {
// 先执行父类的通用工作逻辑
String baseWork = super.work();
// 再加上经理特有的工作
return baseWork + " → 审核项目 → 分配任务 → 评估绩效";
}
}
public class SuperMethodTest {
public static void main(String[] args) {
Manager m = new Manager();
System.out.println(m.work());
}
}
运行输出:
员工正在工作中 → 审核项目 → 分配任务 → 评估绩效
飞翔科技实战:薪资计算模板方法
资深工程师孔蓝用 super.方法() 实现了模板方法模式的雏形:
public class Employee {
private String name;
private double baseSalary;
public Employee(String name, double baseSalary) {
this.name = name;
this.baseSalary = baseSalary;
}
public String getName() { return name; }
public double getBaseSalary() { return baseSalary; }
/**
* 计算月薪的通用逻辑(子类可扩展)
*/
public double computeMonthlySalary() {
return baseSalary; // 基础薪资
}
/**
* 模板方法:打印工资单
* 子类不应重写此方法(用 final 保护)
*/
public final void printPaySlip() {
System.out.println("======== 飞翔科技工资单 ========");
System.out.println("姓名: " + getName());
System.out.println("基础薪资: " + getBaseSalary());
System.out.println("实发月薪: " + computeMonthlySalary()); // 多态调用
System.out.println("================================");
}
}
public class Manager extends Employee {
private double managementBonus;
public Manager(String name, double baseSalary, double managementBonus) {
super(name, baseSalary);
this.managementBonus = managementBonus;
}
@Override
public double computeMonthlySalary() {
// 调用父类的基础薪资计算
double base = super.computeMonthlySalary();
return base + managementBonus; // 加上管理奖金
}
}
public class Developer extends Employee {
private double projectBonus;
private int overtimeHours;
public Developer(String name, double baseSalary, double projectBonus, int overtimeHours) {
super(name, baseSalary);
this.projectBonus = projectBonus;
this.overtimeHours = overtimeHours;
}
@Override
public double computeMonthlySalary() {
double base = super.computeMonthlySalary();
return base + projectBonus + overtimeHours * 150; // 加班费每小时150
}
}
// 薪资计算测试
public class PaySlipTest {
public static void main(String[] args) {
Employee[] staff = {
new Manager("大翔", 50000, 20000),
new Developer("小崔", 25000, 8000, 20),
new Developer("白歌", 35000, 12000, 10)
};
for (Employee emp : staff) {
emp.printPaySlip();
System.out.println();
}
}
}
运行输出:
======== 飞翔科技工资单 ========
姓名: 大翔
基础薪资: 50000.0
实发月薪: 70000.0
================================
======== 飞翔科技工资单 ========
姓名: 小崔
基础薪资: 25000.0
实发月薪: 36000.0
================================
======== 飞翔科技工资单 ========
姓名: 白歌
基础薪资: 35000.0
实发月薪: 48500.0
================================
四、super 与 this 的全面对比
| 对比维度 | this | super |
|---|---|---|
| 含义 | 当前对象的引用 | 当前对象中父类部分的引用 |
| 调用构造方法 | this(参数) 调用本类其他构造方法 | super(参数) 调用父类构造方法 |
| 位置要求 | 必须在构造方法首行 | 必须在构造方法首行 |
| 共存 | this() 和 super() 不能同时出现在首行 | 同上 |
| 访问成员变量 | this.field 访问本类字段(优先于父类) | super.field 访问父类中被隐藏的字段 |
| 调用方法 | this.method() 调用本类方法(优先于父类) | super.method() 调用父类中被重写的方法 |
| 编译器处理 | 编译为 aload_0(局部变量表0号槽) | 编译为 aload_0 + invokespecial |
| 内存本质 | this 和 super 指向同一个对象 | super 只是语法层面的父类视图 |
内存视角
核心理解:
this和super指向同一个对象在堆内存中的不同区域。super只是一种语法机制,让你能访问被子类覆盖的父类成员。
易错场景与避坑指南
反例一:this() 和 super() 同时出现
// ❌ 编译错误
public class Manager extends Employee {
public Manager() {
this("默认"); // 调用本类另一个构造方法
super(); // ❌ 编译错误:对 super 的调用必须是构造器中的第一个语句
}
public Manager(String name) {
super(name);
}
}
纠正:this() 和 super() 都要求位于第一行,不能共存。正确的做法是让 this() 链最终调用到包含 super() 的构造方法。
// ✅ 正确
public class Manager extends Employee {
public Manager() {
this("默认"); // 调用本类有参构造
}
public Manager(String name) {
super(name); // 有参构造中调用 super
}
}
反例二:误以为 super 是父类对象
小崔曾经写出这样的代码:
// ❌ 错误理解
public class Test {
public static void main(String[] args) {
Manager m = new Manager();
// Employee e = super; // ❌ 语法错误:super 不是对象引用
// System.out.println(super); // ❌ 语法错误
}
}
孔蓝纠正:
super是一个关键字,不是一个对象引用变量。你不能把super赋值给变量或作为方法参数传递。它只能在子类内部使用,用来引用父类成员。
反例三:static 上下文使用 super
public class Manager extends Employee {
public static void showParentInfo() {
// super.work(); // ❌ 编译错误:无法在静态上下文中使用 super
}
}
原理:super 指向当前对象的父类部分,而 static 方法没有 this 对象,自然也没有 super。
面试考点
Q1:this() 和 super() 为什么必须在构造方法第一行?
这是 Java 语言规范的语法约束,目的是保证对象在子类构造方法执行之前,父类部分已经完全初始化。java 编译器要求子类构造方法的第一条可执行语句必须是调用父类构造方法(super)或本类另一个构造方法(this),确保构造器链从最顶层依次向下执行,杜绝"半初始化"对象的可能性。
Q2:如果不写 super(),编译器会做什么?
编译器会在子类构造方法的首行自动插入
super()——调用父类的无参构造方法。如果父类没有无参构造方法,编译直接报错。此时必须显式调用父类有参构造方法super(参数列表)。
Q3:super 可以连续调用吗?如 super.super.method()?
不可以。
super只指向直接父类,不能跨级访问祖父类。如果确实需要调用祖父类的方法,可以在直接父类中暴露出一个包装方法。这种限制实际上保护了封装——子类不应该知道祖父类的实现细节。
Q4:子类中访问 this.field 和 super.field 有什么区别?
如果子类没有定义同名 field,两者结果相同(都访问父类字段)。如果子类定义了同名 field(字段隐藏),
this.field访问的是子类字段,super.field访问的是父类被隐藏的字段。注意字段访问是编译时绑定(根据引用类型决定),而方法调用是运行时绑定。