【Java】继承 保姆级别 从0到出师!
🎯 Java 继承详解
📌 继承是 Java 面向对象三大特性之一(封装、继承、多态)
理解继承,就理解了 Java 类与类之间的关系本质
📖 本文导读
本文系统讲解 Java 继承的概念、语法、内存模型、构造方法、super/this 关键字、执行顺序、访问规则以及 final/组合等扩展知识点。适合已掌握类与对象基础的读者
文章目录
1️⃣ 继承的概念 🤪
1.1 为什么需要继承?
先看一个场景:定义多个类,每个类都有共同的属性和行为
// 教师类
public class Teacher {
String name;
int age;
String subject; // 所教科目
public void teach() {
System.out.println(name + "正在上课");
}
public void eat() {
System.out.println(name + "正在吃饭");
}
}
// 学生类
public class Student {
String name;
int age;
String grade; // 年级
public void study() {
System.out.println(name + "正在学习");
}
public void eat() {
System.out.println(name + "正在吃饭");
}
}
发现问题了吗?name、age、eat() 在两个类中完全重复!这违背了 DRY 原则(Don’t Repeat Yourself)
1.2 什么是继承?
继承(Inheritance):将多个类中的**共同属性和行为抽取到一个父类(基类/超类)**中,子类只需要继承父类,就能直接拥有父类的成员,无需重复定义
💡
extends关键字表示"扩展",子类在父类基础上扩展出自己独有的特性
// 父类(Person)
public class Person {
String name;
int age;
public void eat() {
System.out.println(name + "正在吃饭");
}
}
// 教师类 —— 继承 Person
public class Teacher extends Person {
String subject; // 独有属性
public void teach() {
System.out.println(name + "正在上课"); // 直接使用父类的 name
}
}
// 学生类 —— 继承 Person
public class Student extends Person {
String grade; // 独有属性
public void study() {
System.out.println(name + "正在学习"); // 直接使用父类的 name
}
}
1.3 继承的优势
| 优势 | 说明 |
|---|---|
| 减少重复代码 | 公共成员只写一次,子类自动拥有 |
| 提高复用性 | 父类定义一次,多个子类共享 |
| 便于维护 | 修改公共代码只需改父类一处 |
| 体现层次关系 | 符合真实世界的分类结构 |
1.4 继承的类图

2️⃣ 继承的语法 😏
2.1 基本格式
[访问修饰符] class 子类名 extends 父类名 {
// 子类独有成员
}
2.2 完整示例
// 动物父类
public class Animal {
String name;
int age;
public void eat() {
System.out.println(name + "正在吃东西");
}
public void sleep() {
System.out.println(name + "正在睡觉");
}
}
// 狗子类 —— 继承 Animal
public class Dog extends Animal {
String breed; // 品种(子类独有)
// 子类独有方法
public void bark() {
System.out.println(name + "汪汪叫");
}
}
// 测试
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.name = "旺财";
d.age = 3;
d.breed = "哈士奇";
d.eat(); // 继承自父类
d.sleep(); // 继承自父类
d.bark(); // 子类独有
}
}
/* 输出:
旺财正在吃东西
旺财正在睡觉
旺财汪汪叫
*/
2.3 继承的特点
🔑 Java 只支持单继承:一个类只能有一个直接父类,不能同时继承多个类
但可以多层继承(A 继承 B,B 继承 C,A 间接继承了 C)
// 单继承:合法
public class A extends B { }
// 多层继承:合法(A → B → C,逐层继承)
public class C { }
public class B extends C { }
public class A extends B { }
// 多继承:非法!Java 不支持
// public class A extends B, C { } // 编译错误!
2.4 继承的层级关系
Object(所有类的祖先)
│
Person
│
┌───────┴───────┐
│ │
Student Teacher
│ │
│ MathTeacher
📌 在 Java 中,所有类都直接或者间接地继承自
java.lang.Object类。如果不写extends,默认就是extends Object
// 下面两种写法等价:
public class Person { }
// 等价于
public class Person extends Object { }
3️⃣ 继承后成员变量的访问 😎
3.1 访问规则
| 情况 | 示例 | 能否访问 | 原因 |
|---|---|---|---|
| 子类没有,父类有 | this.name |
✅ | 继承而来 |
| 子类有,父类没有 | this.grade |
✅ | 子类自有 |
| 子类和父类都有 | this.name |
✅ | 就近原则,优先子类 |
| 父类想访问自己的成员 | super.name |
✅ | 用 super 明确指定 |
3.2 就近原则与 super 区分
当父类和子类有同名成员变量时:
public class Father {
int money = 1000000;
}
public class Son extends Father {
int money = 100; // 与父类同名
public void show() {
int money = 50; // 与父子类都同名
System.out.println(money); // 50,局部变量
System.out.println(this.money); // 100,子类成员变量
System.out.println(super.money); // 1000000,父类成员变量
}
}
3.3 变量不重名时
public class Father {
String name = "老张";
}
public class Son extends Father {
int age = 18; // 父类没有,子类独有
public void show() {
System.out.println(name); // 继承自父类,输出"老张"
System.out.println(age); // 子类自有,输出 18
}
}
4️⃣ super 关键字 😎
4.1 super 的含义
super 代表父类对象的引用,用于在子类中访问父类的成员
4.2 super 的三种用法
| 用法 | 示例 | 说明 |
|---|---|---|
| 访问父类成员变量 | super.name |
区分父子类同名变量 |
| 调用父类成员方法 | super.eat() |
访问父类被重写的方法 |
| 调用父类构造方法 | super(参数) |
必须在子类构造方法第一行 |
4.3 super 访问成员变量
public class Father {
int a = 10;
}
public class Son extends Father {
int a = 20;
public void test() {
System.out.println(this.a); // 20,子类
System.out.println(super.a); // 10,父类
}
}
4.4 super 调用成员方法
public class Father {
public void eat() {
System.out.println("爸爸在吃满汉全席");
}
}
public class Son extends Father {
@Override
public void eat() {
System.out.println("儿子在吃汉堡薯条");
}
public void callFather() {
this.eat(); // 调用自己的
super.eat(); // 调用父类的
}
}
public class Test {
public static void main(String[] args) {
Son s = new Son();
s.callFather();
}
}
/* 输出:
儿子在吃汉堡薯条
爸爸在吃满汉全席
*/
5️⃣ 子类的构造方法 😏
5.1 构造方法的基本规则
🔑 子类构造方法执行时,必须先调用父类的构造方法!
原因:子类继承自父类,父类的成员也需要初始化。子类的构造方法会默认先调用父类的无参构造
5.2 默认调用父类无参构造
public class Father {
public Father() {
System.out.println("父类构造方法执行");
}
}
public class Son extends Father {
public Son() {
// 这里隐含了一句 super();
System.out.println("子类构造方法执行");
}
}
public class Test {
public static void main(String[] args) {
Son s = new Son();
}
}
/* 输出:
父类构造方法执行
子类构造方法执行
*/
5.3 子类如何调用父类有参构造
如果父类没有无参构造(自定义了有参构造),子类必须显式调用父类的有参构造
public class Father {
String name;
// 自定义了有参构造,无参构造不再自动生成
public Father(String name) {
this.name = name;
System.out.println("父类有参构造:" + name);
}
}
public class Son extends Father {
int age;
// 必须显式调用父类有参构造
public Son(String name, int age) {
super(name); // 调用父类有参构造,必须在第一行
this.age = age;
System.out.println("子类构造:" + name + "," + age);
}
}
public class Test {
public static void main(String[] args) {
Son s = new Son("张三", 18);
}
}
/* 输出:
父类有参构造:张三
子类构造:张三,18
*/
5.4 构造方法调用链
public class Grandpa {
public Grandpa() { System.out.println("Grandpa构造"); }
}
public class Father extends Grandpa {
public Father() { System.out.println("Father构造"); }
}
public class Son extends Father {
public Son() { System.out.println("Son构造"); }
}
// new Son() 时构造链:
// Grandpa() → Father() → Son()
6️⃣ super 和 this 的对比 🤓
6.1 对比表格
| 对比项 | this | super |
|---|---|---|
| 代表 | 当前对象的引用 | 父类对象的引用 |
| 访问成员变量 | this.变量名 |
super.变量名 |
| 调用成员方法 | this.方法名() |
super.方法名() |
| 调用构造方法 | this(参数) |
super(参数) |
| 出现位置 | 任意方法/构造 | 只能在子类中 |
| 默认行为 | 不写也有(构造第一行默认调用父类无参构造) | 不写也有 |
6.2 this 和 this() 的区别
this.name→ 访问成员变量this()→ 调用本类其他构造方法
public class Person {
String name;
int age;
public Person() {
this("未知", 0); // 调用本类有参构造
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
6.3 super 和 this 的内存模型

7️⃣ 继承关系上代码块等的初始化 😕
7.1 继承体系下的执行顺序
在继承关系中,静态代码块、实例代码块、构造方法的执行顺序如下:
🔑 执行顺序(总原则):
父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法
7.2 完整示例
public class Father {
static {
System.out.println("1. 父类静态代码块");
}
{
System.out.println("3. 父类实例代码块");
}
public Father() {
System.out.println("4. 父类构造方法");
}
}
public class Son extends Father {
static {
System.out.println("2. 子类静态代码块");
}
{
System.out.println("5. 子类实例代码块");
}
public Son() {
System.out.println("6. 子类构造方法");
}
public static void main(String[] args) {
System.out.println("=== 第一个对象 ===");
new Son();
System.out.println("=== 第二个对象 ===");
new Son();
}
}
/* 输出:
=== 第一个对象 ===
1. 父类静态代码块
2. 子类静态代码块
3. 父类实例代码块
4. 父类构造方法
5. 子类实例代码块
6. 子类构造方法
=== 第二个对象 ===
3. 父类实例代码块
4. 父类构造方法
5. 子类实例代码块
6. 子类构造方法
*/
7.3 顺序解析
| 阶段 | 内容 | 说明 |
|---|---|---|
| 类加载时 | 父类静态 → 子类静态 | 只执行一次 |
| 创建对象时 | 父类实例代码块 → 父类构造 → 子类实例代码块 → 子类构造 | 每次 new 都执行 |
💡 静态代码块只执行一次(类加载时),实例代码块每次创建对象都执行。
7.4 包含 main 方法时的特殊情况
public class Father {
static { System.out.println("父类静态代码块"); }
{ System.out.println("父类实例代码块"); }
public Father() { System.out.println("父类构造方法"); }
}
public class Son extends Father {
static { System.out.println("子类静态代码块"); }
{ System.out.println("子类实例代码块"); }
public Son() { System.out.println("子类构造方法"); }
public static void main(String[] args) {
// main 方法属于 Son 类,Son 类被加载
// 所以先输出父类和子类的静态代码块
new Son();
}
}
/* 输出:
父类静态代码块 ← Son 类加载,触发父类先加载
子类静态代码块 ← Son 类加载完成
父类实例代码块 ← new Son()
父类构造方法
子类实例代码块
子类构造方法
*/
8️⃣ 再认识 protected 关键字 🧐
8.1 四种访问修饰符回顾
| 修饰符 | 本类 | 同包 | 子类(不同包) | 其他 |
|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ |
protected |
✅ | ✅ | ✅ | ❌ |
| 默认(无修饰符) | ✅ | ✅ | ❌ | ❌ |
private |
✅ | ❌ | ❌ | ❌ |
8.2 protected 的核心价值
protected 的存在,主要是为了让子类在不同包时也能访问父类成员
// com.example 包
public class Father {
protected String name = "张三";
protected void eat() {
System.out.println("吃饭");
}
}
// 同一个包
class SamePackageClass {
void test(Father f) {
f.name; // ✅ 同包,可以访问 protected
f.eat(); // ✅ 同包,可以访问 protected
}
}
// com.other 包,子类
package com.other;
public class Son extends Father {
public void test() {
this.name; // ✅ 子类,可以访问(继承过来的)
super.name; // ✅ 子类,可以用 super
eat(); // ✅ 子类,可以调用
}
}
// com.other 包,非子类
package com.other;
public class Other {
public void test(Father f) {
f.name; // ❌ 不同包非子类,protected 不可见
}
}
8.3 为什么不用 public?
| 场景 | public | protected | private |
|---|---|---|---|
| 同包可访问 | ✅ | ✅ | ✅ |
| 不同包子类可继承 | ✅ | ✅ | ❌ |
| 不同包非子类不可访问 | ❌ | ❌ | ✅ |
💡 设计建议:
- 子类可能需要继承的成员 →
protected- 仅本类内部使用的成员 →
private- 完全开放给所有人 →
public
9️⃣ 继承的方式 😃
9.1 继承方式的分类
继承并非只有一种结构,根据继承链的形态不同,可以分为以下四种方式:
| 继承方式 | 结构特征 | Java 支持 | 典型场景 |
|---|---|---|---|
| 单继承 | 一个类直接继承一个父类 | ✅ | 最常用 |
| 多层继承 | 继承链(A→B→C) | ✅ | 分层结构 |
| Hierarchical 继承 | 多个子类继承同一个父类 | ✅ | 同级分类 |
| 多重继承 | 一个类同时继承多个父类 | ❌(用接口模拟) | — |
9.2 单继承(Single Inheritance)
定义:一个子类只有一个直接父类,是最简单也是最推荐的继承方式
public class Animal {
String name;
public void eat() { System.out.println("吃东西"); }
}
public class Dog extends Animal {
String breed;
public void bark() { System.out.println("汪汪叫"); }
}
Animal
│
│ extends
▼
Dog
💡 Java 官方推荐的继承方式。一个类直接继承一个父类,结构清晰,耦合度可控
9.3 多层继承(Multi-Level Inheritance)
定义:继承链上有三个或更多层。A 继承 B,B 继承 C,那么 A 间接继承了 C
public class LivingThing { // 第 1 层:根
String alive = "活着";
}
public class Animal extends LivingThing { // 第 2 层
String name;
public void breathe() {
System.out.println("呼吸");
}
}
public class Dog extends Animal { // 第 3 层
String breed;
public void bark() {
System.out.println("汪汪叫");
}
}
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.name = "旺财"; // 继承自 Animal
d.alive; // 继承自 LivingThing(间接继承)
d.breathe(); // 继承自 Animal
d.bark(); // 自己定义
}
}
LivingThing(曾祖父)
│
│ extends
▼
Animal(祖父)
│
│ extends
▼
Dog(子类)
⚠️ 继承层次不要超过 3 层,否则耦合过深,维护成本急剧上升
超过 3 层时的典型症状:找一个方法要顺着继承链查好几层,改一处影响半辈子
9.4 Hierarchical 继承(层次继承)
定义:同一个父类被多个子类继承,每个子类以自己的方式实现父类方法
public class Shape { // 唯一父类
public double area() {
return 0;
}
}
public class Circle extends Shape { // 子类1
double r;
@Override
public double area() {
return Math.PI * r * r;
}
}
public class Rectangle extends Shape { // 子类2
double width, height;
@Override
public double area() {
return width * height;
}
}
public class Triangle extends Shape { // 子类3
double base, height;
@Override
public double area() {
return 0.5 * base * height;
}
}
Shape(父类)
┌────────┴────────┬─────────┐
│ │ │
▼ ▼ ▼
Circle Rectangle Triangle
典型应用场景:
- 图形系统:
Shape→Circle/Rectangle/Triangle - 动物系统:
Animal→Dog/Cat/Bird - 支付系统:
Payment→Alipay/WechatPay/CreditCardPay
9.5 多重继承 —— 为什么 Java 不支多继承?
定义:一个子类同时继承多个父类(如 C++ 的多继承)。
Father1 Father2
│ │
└───────┬───────┘
▼
Son(同时继承两个父亲)
⚠️ Java 不支持多重继承!
为什么?假设 Son 同时继承 Father1 和 Father2,而两者都有一个
eat()方法:public class Father1 { public void eat() { } } public class Father2 { public void eat() { } } public class Son extends Father1, Father2 { } // 编译错误!Son 调用
eat()时,编译器不知道该调用哪个版本,产生菱形继承问题(Diamond Problem)
9.6 用接口模拟多重继承
虽然类不能多继承,但 Java 提供了**接口(interface)**来弥补这一缺陷
// 两个"能力接口"(相当于两个"父类")
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// Duck 同时拥有两个能力:多实现(implements)
public class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子飞");
}
@Override
public void swim() {
System.out.println("鸭子游");
}
}
Flyable(接口1) Swimmable(接口2)
│ │
└───────────┬───────────┘
▼
Duck(同时实现两个接口)
🔑 类继承(extends)和接口实现(implements)的区别:
对比项 extends implements 父类数量 只能一个 可以多个 关系 “是-a”(is-a) “能-a”(can-do) 内容 继承属性+方法 只定义方法签名 复用方式 直接复用代码 子类提供实现
9.7 四种继承方式总结
① 单继承(Single) ② 多层继承(Multi-Level)
A LivingThing
│ │
│ extends Animal(间接继承)
▼ │
B │
Dog(间接继承)
③ Hierarchical 继承 ④ 多重继承(Java不支持)
Shape Father1 Father2
┌──┴──┐ └────┬────┘
▼ ▼ Son ❌
Circle Rectangle
④' 替代方案(接口)
Flyable Swimmable
└────────┬────────┘
▼
Duck ✅
🔟 方法重写(Override) 😃
10.1 什么是方法重写?
方法重写(Override):子类继承父类后,可以重新定义父类中已有的方法,从而覆盖父类的实现,子类提供自己的特殊行为
10.2 方法重写的规则
| 规则 | 要求 |
|---|---|
| 方法名 | 必须与父类方法名完全相同 |
| 参数列表 | 必须与父类方法完全相同(个数、类型、顺序) |
| 返回值类型 | 必须与父类方法相同或是其子类型(协变返回) |
| 访问修饰符 | 子类访问修饰符 ≥ 父类(不能更严格) |
| 抛出的异常 | 不能抛出比父类更新的或更宽的受检异常 |
[!IMPORTANT]
🔑 私有方法(private)不能重写! 子类中定义与父类 private 方法同名的方法,是新方法,不是重写
10.3 重写示例
public class Phone {
public void call(String number) {
System.out.println("用手机打电话:" + number);
}
// private 方法 —— 不能被重写
private void connect() {
System.out.println("建立通话连接");
}
}
public class SmartPhone extends Phone {
// ✅ 这叫重写:方法名、参数、返回值都一致
@Override
public void call(String number) {
System.out.println("用智能手机打电话:" + number);
System.out.println("同时打开免提...");
}
// ✅ 这是新方法,不是重写(因为父类是 private)
private void connect() {
System.out.println("建立4G视频通话连接");
}
}
10.4 @Override 注解
@Override 是编译器注解,加在重写方法上,告诉编译器"这个方法应该是重写父类的"。如果重写不合法(比如方法名拼错了),编译器会直接报错。
public class Father {
public void play() {
System.out.println("父亲在打太极拳");
}
}
public class Son extends Father {
// ✅ 正确重写,加了 @Override 编译通过
@Override
public void play() {
System.out.println("儿子在打游戏");
}
// ❌ 拼错了!编译器会报错(因为父类没有这个方法)
@Override
public void ploy() { // 编译错误!没有匹配到父类方法
System.out.println("儿子在打游戏");
}
}
[!TIP]
💡 强烈建议始终加上
@Override,不要省略。这是防御性编程,可以帮你尽早发现错误
10.5 super.方法名() 调用父类版本
重写后,如果子类的重写方法想复用父类的实现,可以用 super.方法名() 调用:
public class Animal {
public void cry() {
System.out.println("动物发出叫声");
}
}
public class Dog extends Animal {
@Override
public void cry() {
super.cry(); // 先调父类
System.out.println("汪汪汪"); // 再加自己的
}
}
public class Cat extends Animal {
@Override
public void cry() {
System.out.println("喵喵喵");
// 这里没调用 super.cry(),完全替换了父类行为
}
}
public class Test {
public static void main(String[] args) {
new Dog().cry();
new Cat().cry();
}
}
/* 输出:
动物发出叫声
汪汪汪
喵喵喵
*/
10.6 重写与重载的区别
| 对比项 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 英文名 | Override | Overload |
| 发生位置 | 父子类之间 | 同一类中 |
| 方法名 | 必须相同 | 必须相同 |
| 参数列表 | 必须相同 | 必须不同 |
| 返回值类型 | 相同或协变 | 可以不同(不关心) |
| 访问修饰符 | 子类 ≥ 父类 | 可以不同(不关心) |
| 抛出异常 | 不能比父类更宽 | 可以不同(不关心) |
public class Father {
public void test() { } // 原始版本
public void test(int a) { } // 重载(参数不同)
}
public class Son extends Father {
@Override
public void test() { } // 重写(无参数)
// public void test() { } // 重载 ❌ 参数相同=重写,不是重载
}
10.7 不能被重写的方法
以下方法不能被重写:
| 情况 | 说明 | 示例 |
|---|---|---|
final 修饰的方法 |
父类明确声明不允许重写 | public final void show() { } |
private 方法 |
私有方法对子类不可见,根本不存在重写 | private void show() { } |
static 方法 |
静态方法属于类,不存在多态override | public static void show() { } |
| 构造方法 | 构造方法不能被继承,更不能重写 | — |
[!CAUTION]
⚠️ 常见误区:子类写了与父类 static 方法同名的方法,不是重写,是子类重新定义了一个新的 static 方法。 通过子类引用调用时,调用的是子类的版本,而非父类版本(但这与重写的多态行为完全不同,实践中容易产生困惑,应避免这样写)
1️⃣1️⃣ final 关键字 😕
11.1 final 的作用
final 表示"最终的,不可改变的"。一旦赋值,就不能再改变。
11.2 final 修饰变量
11.2.1 基本类型变量
final 修饰基本类型变量时,其值不能改变:
public class Test {
final int NUM = 10;
public void method() {
// NUM = 20; // ❌ 编译错误!final 变量不能重新赋值
System.out.println(NUM); // ✅ 可以读取
}
}
11.2.2 引用类型变量
final 修饰引用类型变量时,引用本身(地址)不能改变,但对象内部的属性可以修改:
public class Dog {
String name = "旺财";
}
public class Test {
final Dog DOG = new Dog();
public void method() {
// DOG = new Dog(); // ❌ 编译错误!引用不能重新指向
DOG.name = "大黄"; // ✅ 可以修改对象的属性
System.out.println(DOG.name); // 输出:大黄
}
}
11.2.3 成员变量的赋值时机
final 修饰的成员变量,必须在以下四个时机之一完成赋值:
| 时机 | 示例 | 说明 |
|---|---|---|
| 声明时直接赋值 | final int A = 10; |
最简单 |
| 构造方法中赋值 | 在构造方法里赋值 | 每个构造都要赋 |
| 构造代码块中赋值 | 在 {} 代码块中赋值 |
一次赋给所有构造 |
| 初始化方法中赋值 | final + 静态方法(static final) |
静态常量场景 |
public class Person {
// 方式1:声明时赋值
final String NAME = "未知";
// 方式2:构造代码块中赋值
final int AGE;
{
AGE = 0; // 在实例代码块中赋值
}
// 方式3:构造方法中赋值
final String GENDER;
public Person(String gender) {
this.GENDER = gender; // 在构造方法中赋值
}
}
11.3 final 修饰方法
final 修饰方法时,该方法不能被重写,但可以正常继承和调用:
public class Father {
public final void show() {
System.out.println("父类的 show 方法");
}
public void run() {
System.out.println("父类的 run 方法");
}
}
public class Son extends Father {
// ✅ 可以继承 show,但不能重写
// @Override public void show() { } // ❌ 编译错误!final 方法不能重写
// ✅ 普通方法可以重写
@Override
public void run() {
System.out.println("子类的 run 方法");
}
}
[!TIP]
💡 什么时候用
final方法?当你确定某个方法不需要被子类修改,且子类重写可能产生错误或安全隐患时,标记为final
11.4 final 修饰类
final 修饰类时,该类不能被继承,没有子类:
public final class String {
// java.lang.String 是 final 类
// public class MyString extends String { } // ❌ 编译错误!
}
public final class Constants {
public static final double PI = 3.14159;
public static final String APP_NAME = "MyApp";
}
// ❌ 编译错误!不能继承 final 类
// public class MyConstants extends Constants { }
[!IMPORTANT]
🔑 常见 final 类:
java.lang.String、java.lang.System、所有包装类(Integer、Double 等)
11.5 final 的四大场景总结
| 修饰对象 | 效果 | 示例 |
|---|---|---|
| 变量(基本类型) | 值不可变,等价于常量 | final int NUM = 100; |
| 变量(引用类型) | 引用不可变(指向不变),对象内部可变 | final Dog D = new Dog(); |
| 方法 | 方法不能被重写,但可继承和调用 | public final void show() { } |
| 类 | 类不能被继承(没有子类) | public final class A { } |
11.6 static final —— 类常量
static final 组合表示类级别的常量:整个类只有一份,且不可改变。命名规范为全大写,用下划线分隔:
public class MathUtils {
// 类常量:通过类名直接访问
public static final double PI = 3.1415926;
public static final int MAX_COUNT = 1000;
// 访问方式
public void test() {
System.out.println(PI); // ✅ 本类中直接访问
System.out.println(MathUtils.PI); // ✅ 类名.常量名
}
}
public class Test {
public static void main(String[] args) {
System.out.println(MathUtils.PI); // ✅ 类名.常量名
// MathUtils.PI = 3.14; // ❌ 编译错误!类常量不可改
}
}
1️⃣2️⃣ 继承与组合 🤔
12.1 组合的概念
组合(Composition):在类中直接使用另一个类的对象作为成员变量,而不是通过继承来复用代码
12.2 继承 vs 组合
| 对比项 | 继承 | 组合 |
|---|---|---|
| 关系 | “是”(is-a) | “有”(has-a) |
| 语法 | extends |
直接成员变量 |
| 耦合度 | 高(父子紧耦合) | 低(松耦合) |
| 灵活性 | 低(单继承限制) | 高(可以组合多个) |
| 适用场景 | 真正的分类关系 | 临时组合复用 |
12.3 组合示例
// 继承方式:"学生是动物"(语义上不太对)
public class Student extends Animal {
String grade;
}
// 组合方式:"学生有一台电脑"(语义更准确)
public class Computer {
String brand;
int price;
public void start() {
System.out.println(brand + "电脑开机");
}
}
public class Student {
String name;
int age;
// 组合:学生有一台电脑
Computer computer = new Computer();
public void coding() {
System.out.println(name + "在写代码");
computer.start();
}
}
11.4 为什么优先用组合而不是继承?
⚠️ 继承的坑:子类继承了父类所有方法,即使父类的某个方法在子类语义下完全不合理
组合可以选择性使用父类的功能,而继承则是全盘接收
// 继承的坑:假设我们想让学生有"站立"的功能
public class Human {
public void stand() { System.out.println("站立"); }
}
// 用继承:学生继承了 stand,但同时也继承了 stand 的所有实现细节
public class Student extends Human { }
// 用组合:只获取需要的部分
public class Student {
Human human = new Human(); // 需要时调用 human.stand()
}
12.5 组合的高级用法(依赖注入)
public class Student {
// 组合:可以注入不同类型的动物
private Animal animal;
public void setAnimal(Animal animal) {
this.animal = animal;
}
public void play() {
animal.cry(); // 多态调用
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setAnimal(new Dog()); // 玩狗
s.play();
s.setAnimal(new Cat()); // 换成猫
s.play();
}
}
1️⃣3️⃣ 继承的内存模型 😲
13.1 对象在内存中的结构
public class Father {
int fatherVar = 10;
public void fatherMethod() {
System.out.println("父类方法");
}
}
public class Son extends Father {
int sonVar = 20;
public void sonMethod() {
System.out.println("子类方法");
}
}
public class Test {
public static void main(String[] args) {
Son s = new Son();
s.fatherVar = 30; // 继承来的
s.sonVar = 40; // 自有的
s.fatherMethod(); // 调用继承的方法
s.sonMethod(); // 调用自有的方法
}
}
13.2 内存图解

🔑 关键点:
- 子类对象在堆中同时包含父类和子类两部分区域
s变量在栈中,存储的是对象的首地址fatherMethod()和sonMethod()都存在方法区,子类对象不复制方法,只引用方法区
1️⃣4️⃣ 继承的注意事项 😵
14.1 子类不能继承父类的哪些成员?
| 不能继承的 | 原因 |
|---|---|
private 成员 |
私有,访问不到 |
| 构造方法 | 构造方法是用来创建对象的,每个类用自己的构造 |
| 同一包中默认修饰符的成员 | 包访问权限 |
final 修饰的父类 |
final 类不能被继承 |
public class Father {
private String secret = "秘密"; // ❌ 子类不能继承
String defaultVar = "默认"; // 同包可继承
public String publicVar = "公开"; // ✅ 可以继承
}
public class Son extends Father {
public void test() {
// secret 不可见!
System.out.println(defaultVar); // ✅
System.out.println(publicVar); // ✅
}
}
14.2 继承的层次不宜过深
// 不推荐:层次过深(继承链太长)
class A { }
class B extends A { }
class C extends B { }
class D extends C { }
class E extends D { }
class F extends E { }
⚠️ 继承层次过深的危害:
- 理解成本高:查一个方法要沿着继承链往上找很久
- 耦合度高:改父类影响所有子类
- JVM 加载慢:层次深意味着要加载更多类
14.3 继承与多态的关系
继承是实现多态的基础之一。关于多态的详细内容,将在下一篇文章中详细讲解
// 多态:父类引用指向子类对象
public class Test {
public static void main(String[] args) {
// 父类引用 f,指向子类对象(new Dog())
Animal a = new Dog(); // 多态
a.cry(); // 运行的是 Dog 的 cry()(父类引用调用子类重写方法)
// a.bark(); // ❌ 编译错误!a 的编译类型是 Animal,Animal 没有 bark()
}
}
1️⃣5️⃣ 最佳实践与设计建议 🧠
15.1 继承的"is-a"原则
💡 继承关系判断标准:子类"是一种"父类吗?
Dog is an Animal✅ 继承合理Student is a School❌ 继承不合理Son is a Father❌ 继承不合理(儿子不是父亲)
15.2 设计原则
| 原则 | 说明 |
|---|---|
| 里氏替换原则(LSP) | 子类对象可以替换父类位置,程序仍然正确 |
| 优先组合 | 能用组合解决的问题,不要轻易用继承 |
| 最小权限原则 | 成员尽量用 private,需要时通过 protected 或方法暴露 |
| 接口分离 | 一个类继承太多功能 → 拆分成多个接口 |
15.3 经典错误示例
// ❌ 错误:数学老师不是学生,却继承了 Student 类
public class Student {
String name;
int grade;
}
public class MathTeacher extends Student { // 语义混乱!
String subject;
}
// ✅ 正确:数学老师是教师的一种
public class Teacher extends Person {
String subject;
}
15.4 正确的继承设计
// 正确的继承体系
public abstract class Person {
protected String name;
protected int age;
public abstract void work(); // 不同人有不同工作
}
public class Student extends Person {
String grade;
@Override
public void work() {
System.out.println("学习");
}
}
public class Teacher extends Person {
String subject;
@Override
public void work() {
System.out.println("教书");
}
}
📊 小结
| 知识点 | 核心内容 |
|---|---|
| 继承概念 | extends,子类继承父类,拥有父类成员 |
| 继承特点 | 单继承,多层继承;所有类间接继承 Object |
| 成员访问 | 就近原则,super 访问父类 |
| super 关键字 | 访问父类变量、方法、构造方法 |
| 构造方法 | 默认调用父类无参构造;父类无有参构造时需显式 super() |
| 执行顺序 | 父静态 → 子静态 → 父实例 → 父构造 → 子实例 → 子构造 |
| protected | 同包 + 不同包子类可访问 |
| 方法重写 | @Override,子类覆盖父类方法,super.方法名() 调用父版 |
| final | 不可变:修饰变量/方法/类 |
| 继承 vs 组合 | is-a 用继承,has-a 用组合 |
| 内存模型 | 堆中子类对象包含父类区域 |
✍️ 写在最后
继承是 Java 代码复用的核心手段,但也是最容易被滥用的特性。优先使用组合,谨慎使用继承,是面向对象设计的经验之谈
继承的下一个知识点是多态 —— 让"父类引用指向子类对象"成为可能,真正发挥 OOP 的威力。敬请期待!
ps: IDEA 快捷键 Ctrl+Alt+L 可以格式化对齐代码
可能读者代码敲着就会产生其他的疑惑 这些疑惑可能是后续多态 接口的知识
如有问题或建议,欢迎在评论区留言交流! 🙌
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)