当前位置: 首页 > news >正文

Day15 --【Java全栈】面向对象编程[3]继承

继承(Inheritance)

全程沿用奶茶店统一类比体系,和封装、访问修饰符、this 知识点完全串联;从问题引入到语法实现、从核心特性到常见误区逐层拆解。


先思考一个问题: 如果我们要写「珍珠奶茶类」「芋泥奶茶类「鲜果茶类」三个类,它们都有饮品名称、价格、温度这些通用属性,也都有获取价格、饮用这些通用方法。如果每个类都把这些属性和方法重新写一遍,会出现什么问题?

最直接的问题就是:代码大量重复,修改通用逻辑要改好几个地方,维护成本高,还容易改漏。 面向对象里专门用来解决「代码复用、层级抽象」问题的特性,就是继承。


一、继承是什么

💡 通俗理解

奶茶店不会给每一款饮品单独从零设计配方模板,而是先做一份「通用饮品基础模板」,上面写清楚所有饮品都有的要素:名称、定价规则、基础制作步骤。 具体的珍珠奶茶、芋泥奶茶、果茶,都直接沿用这份基础模板,只需要补充自己独有的配料、调整专属的制作步骤就行,不用从零开始写。

对应到代码里:

  • 通用饮品模板 = 父类(基类 / 超类):抽取所有子类共有的属性和方法,是抽象的通用模板

  • 具体奶茶品类 = 子类(派生类):继承父类的所有通用内容,再扩展自己的特有属性和方法

一句话理解:继承就是「子类复用父类的属性和方法,不用重复写,还能按需扩展」。

🔴 记忆:正式定义

继承是面向对象三大核心特性之一,是实现代码复用的核心机制。它允许一个类(子类)通过关键字继承另一个类(父类)的非私有属性与方法,子类可以直接复用父类成员,也可以定义自己的专属成员,甚至重写父类的方法逻辑。

继承的三大核心价值

  1. 代码复用,减少冗余 通用属性和方法只在父类写一次,所有子类直接复用,不用重复编写,大幅减少重复代码。

  2. 层级清晰,便于扩展 把通用逻辑向上抽取到父类,子类只关注自己的专属逻辑,结构分层清晰,新增品类时只需要继承父类、补充独有内容即可。

  3. 为多态提供基础 继承是多态的前提,没有父子类的继承关系,多态就无法实现(多态后续会详细讲)。


二、继承的基础语法

1. 核心关键字:extends

Java 中通过extends关键字实现继承,语法格式:

// 子类 extends 父类 public class 子类类名 extends 父类类名 { // 子类自己的属性和方法 }

2. 基础代码示例(奶茶店场景)

先定义父类:所有饮品的通用模板

/** * 父类:基础饮品类 * 抽取所有饮品共有的属性和方法 */ public class BaseDrink { // 通用属性:所有饮品都有名称、价格、温度 private String name; private double price; private int temperature; // 通用方法:获取价格 public double getPrice() { return price; } public void setPrice(double price) { if(price < 0) { System.out.println("价格不能为负"); return; } this.price = price; } // 通用方法:饮用 public void drink() { System.out.println("饮用" + name + ",温度" + temperature + "度"); } // 通用get/set方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getTemperature() { return temperature; } public void setTemperature(int temperature) { this.temperature = temperature; } }

再定义子类:奶茶类,继承基础饮品类

/** * 子类:奶茶类 * 继承BaseDrink,复用所有通用属性和方法,只需要补充奶茶独有的内容 */ public class MilkTea extends BaseDrink { // 子类独有属性:茶底 private String teaBase; // 子类独有方法:加小料 public void addTopping(String topping) { System.out.println("给" + getName() + "添加" + topping); } // 茶底的get/set public String getTeaBase() { return teaBase; } public void setTeaBase(String teaBase) { this.teaBase = teaBase; } }

👉 效果:MilkTea类里虽然只写了茶底和加小料的方法,但它自动拥有了父类BaseDrink的 name、price、temperature 属性,以及 drink、getPrice 等所有 public 方法,创建对象后可以直接调用:

public class Test { public static void main(String[] args) { MilkTea pearlTea = new MilkTea(); // 调用父类继承来的方法 pearlTea.setName("珍珠奶茶"); pearlTea.setPrice(12); pearlTea.setTemperature(60); // 调用子类自己的方法 pearlTea.setTeaBase("红茶"); pearlTea.addTopping("珍珠"); // 调用父类继承来的方法 pearlTea.drink(); } }

3. 子类到底能继承到什么?

继承不是父类所有内容都原封不动拿过来,能不能继承,完全由访问修饰符决定,和之前讲的权限规则完全对应:

父类成员的修饰符子类能否直接访问
public✅ 可以直接访问
protected✅ 可以直接访问(继承的核心设计之一)
default(包私有)同包子类可以,不同包子类不行
private❌ 不能直接访问

💡 补充:父类的 private 属性子类不是 “完全拿不到”,只是不能直接用this.属性名访问,依然可以通过父类提供的 public get/set 方法间接操作,这也是「封装 + 继承」配合的典型场景。


三、继承的核心规则与 super 关键字

1. Java 是单继承机制

Java 中一个子类只能有一个直接父类,不支持同时继承多个类(比如不能写class A extends B, C)。 但支持多层继承:A 继承 B,B 继承 C,A 会同时拥有 B 和 C 的所有可继承成员,形成「爷爷 → 爸爸 → 儿子」的继承树。 比如:BaseDrinkMilkTeaPearlMilkTea

2. 构造方法不能被继承

父类的构造方法,子类不能继承复用。但子类构造方法执行时,默认会先调用父类的无参构造方法,相当于先初始化父类的通用属性,再初始化子类的专属属性。 这个默认调用的动作,就是靠super()实现的。

3. super 关键字:父类对象的引用

super代表当前对象中父类部分的引用,专门用来访问父类的成员,和我们之前讲的this是对应关系。 它有三大核心用法,和this一一对应:

用法作用
super.属性名访问父类的成员变量
super.方法名(参数)调用父类的成员方法
super(参数)调用父类的构造方法
用法 1:调用父类构造方法

子类构造方法的第一行,默认会有一个隐藏的super();,自动调用父类的无参构造。 如果父类没有无参构造(比如手动写了有参构造),子类就必须手动用super(参数)调用父类的有参构造,否则编译报错。

代码示例:

// 父类:写了有参构造,没有无参构造 public class BaseDrink { private String name; private double price; public BaseDrink(String name, double price) { this.name = name; this.price = price; } } // 子类:必须手动调用父类有参构造 public class MilkTea extends BaseDrink { private String teaBase; public MilkTea(String name, double price, String teaBase) { // 必须写在第一行,调用父类有参构造,先初始化父类属性 super(name, price); this.teaBase = teaBase; } }

🔴 记忆:super(参数)this(参数)一样,都必须写在构造方法的第一行,所以二者不能同时出现在同一个构造方法里。

用法 2:调用父类的普通方法

子类重写父类方法后,如果还想用到父类原有的逻辑,可以用super.方法名()调用。 这个用法在方法重写里非常常用,下面讲重写的时候会结合示例。


四、方法重写(Override)

子类继承了父类的方法,但父类的通用逻辑不一定完全符合子类的需求,这时候子类可以重新实现父类的同名方法,这就是方法重写,也叫方法覆盖。

💡 通俗理解

父类的通用模板里写了「基础制作步骤」,但珍珠奶茶有自己的专属制作流程,就可以把制作方法重写一遍,保留通用部分,补充专属步骤,不用受父类逻辑的限制。

1. 重写的代码示例

// 父类 public class BaseDrink { public void make() { System.out.println("准备基础饮品原料"); } } // 子类重写make方法 public class MilkTea extends BaseDrink { // @Override 是注解,用来标记这是重写的方法,编译器会帮你检查格式是否正确 @Override public void make() { // 可以选择先调用父类的原有逻辑,也可以完全重写 super.make(); System.out.println("加入茶底和牛奶"); System.out.println("添加配料摇匀"); } }

2. 重写的核心规则(面试高频)

重写不是随便写个同名方法就行,必须遵守以下规则,否则就不是重写:

  1. 方法签名必须一致:方法名、参数列表、参数顺序必须和父类完全相同
  2. 返回值类型一致:基本类型必须相同,引用类型可以是父类返回值的子类
  3. 访问权限不能更严格:父类是 public,子类就不能写成 protected 或 private
  4. 抛出的异常不能更大:子类方法不能抛出比父类更宽泛的异常类型

3. 重写 vs 重载:别再混淆

很多不熟悉的同学容易把重写和重载搞混,二者完全不是一个维度的概念,对比表一目了然:

对比项方法重写(Override)方法重载(Overload)
适用场景父子类之间同一个类内部
方法签名必须完全一致方法名相同,参数列表必须不同
返回值必须一致(或子类)无要求
访问权限不能更严格无要求
核心作用子类重定义父类方法逻辑,实现运行时多态同一个方法名适配不同参数,提升易用性

🔴 记忆:一句话区分 —— 重写是父子类的 “覆盖”,重载是同个类的 “同名不同参”。


五、所有类的根父类:Object

Java 中有一个特殊的类:java.lang.Object,它是所有类的顶级父类。 哪怕你写类的时候没写extends,编译器也会默认让它继承 Object 类。也就是说,所有 Java 对象都拥有 Object 类中定义的方法,最常用的有三个:

  1. toString():把对象转成字符串表示
  2. equals():判断两个对象内容是否相等
  3. hashCode():返回对象的哈希值

这些方法所有类都能直接用,也可以根据需求重写,比如我们经常重写实体类的toString()equals()方法,就是继承自 Object 的重写应用。


六、不熟悉的同学容易踩的认知误区

误区 1:继承就是把父类的代码复制粘贴到子类里

✅ 正解:不是复制,是复用引用。子类对象里包含了一个完整的父类对象结构,二者是包含关系,不是复制关系,父类修改后所有子类都会同步生效。

误区 2:private 属性不能被继承,所以子类完全没有

✅ 正解:private 属性子类不能直接访问,但它依然存在于子类对象中,只是受封装保护,只能通过父类的 public 方法间接操作。

误区 3:继承越多、层级越深越好

✅ 正解:继承会增强类之间的耦合度 —— 父类修改会影响所有子类,层级过深会导致代码难以维护。 继承只适合真正的「is-a」关系(奶茶是饮品),不要为了单纯复用代码就强行用继承,复用代码还可以用组合、工具类等更灵活的方式。

误区 4:只要方法名一样就是重写

✅ 正解:必须满足参数列表、返回值、访问权限等所有规则才是重写,建议加上@Override注解,让编译器帮你校验格式,避免写错了自己还不知道。


七、和之前知识点的串联

  1. 关联封装:父类的私有属性靠封装隐藏,子类通过公共 get/set 方法间接访问,二者配合实现「内部数据保护 + 外部代码复用」。
  2. 关联访问修饰符protected权限就是专门为继承设计的 —— 让子类能访问父类成员,同时对外保持隐藏。
  3. 关联 this 关键字this指向当前对象本身,super指向当前对象的父类部分,二者配合完成对象内部的成员访问。
  4. 关联方法重载:重载是同类内的多同名方法,重写是父子类的方法覆盖,二者是完全不同的语法特性。
http://www.cnnetsun.cn/news/3443473.html

相关文章:

  • 百度输入法Linux版深度适配统信UOS技术解析
  • Python数据科学核心工具链与实战技巧详解
  • 华为MetaERP 跨OU的供应商地点与库存组织映射是P2P多OU架构设计的核心难点之一。Oracle EBS原生并未提供直接的“供应商地点→库存组织”映射表,这需要在扩展层自行设计。下面给出经过生产
  • Action Token设计:VLA模型与物理世界的神经接口
  • 世界行动模型WAM与VLA范式对比:泛化能力的四个刚性维度
  • AI模型训练全流程实战:从数据准备到生产部署
  • C++字符串分割六种方法详解:从基础实现到性能优化实战
  • AI辅助3D建模:Claude与Blender MCP插件实战指南
  • 《考研408数据结构》第六章(6.4 图的应用)复习笔记
  • UOS操作系统网络测试与端口连通性实战指南
  • Ubuntu系统清理:彻底卸载软件包的实用指南
  • 中间件的“价值重估”:传统同质化竞争终结,AI智能编排时代开启
  • WMIC:Windows系统管理的命令行利器与实战技巧
  • TRAE+Milvus+MCP:中文开发者零门槛向量数据库实战指南
  • Cesium 绘制图形并导出geojson教程
  • WorldVLN:自回归世界动作模型实现真实无人机零样本导航
  • 电脑性能优化全攻略:从系统清理到硬件升级
  • 投资决策流程与龙头战法:从跟风到自主实战指南
  • Java 面向对象基础笔记:设计带操作界面的应用程序
  • 具身智能落地实战:从物理约束到确定性控制的四年技术路线
  • 怎样高效美化OpenWrt界面:Argon主题专业实战技巧
  • 第三十八课——移动App功能测试实战指南
  • ESP32 Arduino核心开发实战:从基础外设到高级协议深度解析
  • 老旧Windows电脑性能优化全攻略
  • 金融AI模型蒸馏技术与轻量化部署实践
  • macOS DMG转ISO:虚拟机部署与系统维护指南
  • 学生成绩课表管理系统:从数据流设计到工程实践
  • 全志D1s开发板RISC-V裸机开发环境搭建指南
  • 具身智能数据基建:从物理信号到鲁棒决策的全链路实践
  • 大模型GEO内容更新频率:3个节奏规律提收录权重,零成本提30%引用率附更新计划表