乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

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 的全面对比

对比维度thissuper
含义当前对象的引用当前对象中父类部分的引用
调用构造方法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 访问的是父类被隐藏的字段。注意字段访问是编译时绑定(根据引用类型决定),而方法调用是运行时绑定。


上一页
继承
下一页
final 关键字