资讯动态

【Java基础|Day10】Java面向对象之继承篇:从为什么需要继承到内存原理

发布时间:2026/8/22 23:42:36 来源:尧图企业网站定制
文章目录 今日学习目标一、为什么需要继承二、继承的概念与认识2.1 什么是继承2.2 语法extends关键字2.3 继承的好处2.4 什么时候使用继承三、如何在代码中设计继承关系四、Java继承的特点五、子类到底能继承父类中的哪些内容内存原理深度解析5.1 构造方法的继承与访问5.1.1 继承5.1.2 访问5.2 成员变量的继承与访问5.2.1 继承5.2.2 访问5.2.2.1 就近原则与super/this访问5.3 成员方法的继承与访问5.3.1 继承虚方法表5.3.2 访问5.3.2.1 就近原则与方法重写六、this 与 super 总结 今日学习目标理解为什么需要继承及其核心价值掌握继承的语法、特点及设计原则深入剖析子类到底能继承父类的哪些内容构造方法、成员变量、成员方法理清 super 和 this 的用法与底层原理学会方法重写的规则与应用场景一、为什么需要继承在面向对象编程中我们经常需要定义一些具有明显共性的类。例如我们可能有一个Student类和一个Teacher类它们都包含name、age等属性都有eat()和sleep()等方法。如果为每个类都独立编写这些相同的属性和方法不仅代码冗余而且维护困难。因此我们需要一种机制来抽取共性提高代码的复用性。这就是继承的由来。通过继承我们可以将这些公共的属性和方法提取到一个父类 Person类 中然后让Student和Teacher继承这个父类从而自动获得父类中的内容。二、继承的概念与认识2.1 什么是继承继承是面向对象三大特性之一封装、继承、多态。它允许我们基于一个类父类/基类/超类创建一个新类子类/派生类。子类可以复用父类的属性和方法并在此基础上添加新的功能。或者说可以把多个子类中重复的代码抽取到父类中。2.2 语法extends关键字在Java中通过extends关键字建立继承关系。publicclassStudentextendsPerson{}子类派生类Student父类基类/超类Person对比cC中使用class 子类 : 继承方式 父类来实现继承2.3 继承的好处提高代码复用性将多个子类的重复代码抽取到父类中避免重复编写。便于功能扩展子类可以在父类基础上添加新功能。形成体系为多态提供了基础。从父类继承过来的表现其共性而新增的成员体现了其个性。2.4 什么时候使用继承当类与类之间存在相同的内容共性并满足子类是父类的一种就可以考虑使用继承来优化代码。这里强调子类是父类的一种两个类之间满足 “is-a”是一个关系时才适合使用继承。学生 是一个 人 → 可以继承汽车 是一个 交通工具 → 可以继承⚠️注意不能仅因为有共同的行为就滥用继承。❌ 错误示例狗有“跑”的行为交通工具也有“跑”的行为就让狗继承交通工具。狗不是交通工具“跑”只是它的一项能力。满足存在相同的内容共性但是并不满足子类是父类的一种。如果将父类抽象得过于宽泛如所有事物都继承自一个“事物”类则粒度太粗无法体现具体概念间的联系反而会使设计变得混乱。正确的抽象应该聚焦于某一组相关的概念。三、如何在代码中设计继承关系推荐采用 “从下往上抽取共性从上往下实现代码” 的思路。设计步骤列出所有需要的类并找出它们的共性。画图从子类开始将共性逐层向上抽取形成父类。编码按照从上到下的顺序先写父类再写子类实现代码。代码示例动物继承体系// 动物类所有动物都有吃饭和喝水classAnimal{publicvoideat(){System.out.println(吃饭);}publicvoiddrink(){System.out.println(喝水);}}// 猫类继承自动物增加抓老鼠classCatextendsAnimal{publicvoidcatchMouse(){System.out.println(抓老鼠);}}// 狗类继承自动物增加看家classDogextendsAnimal{publicvoidguard(){System.out.println(看家);}}// 布偶猫继承猫类classRagdollextendsCat{// 无需额外方法}// 中国狸花猫继承猫类classChineseLiHuaextendsCat{// 无需额外方法}// 哈士奇继承狗类增加拆家个性classHuskyextendsDog{publicvoiddemolishHome(){System.out.println(拆家);}}// 泰迪继承狗类增加蹭一蹭个性classTeddyextendsDog{publicvoidrub(){System.out.println(蹭一蹭);}}四、Java继承的特点单继承Java中一个类只能有一个直接父类即不支持多继承。这是为了避免多个父类中存在同名成员时产生的二义性。为什么Java不支持多继承如果子类继承的两个父类中有相同的方法签名子类在调用该方法时编译器无法确定应该使用哪个父类的方法导致混乱。 例如对比CC 支持多继承C允许一个类继承多个父类 格式class 子类继承方式 父类1继承方式父类2…但遇到同名成员时需要加作用域区分 父类名::成员 。尽管如此多继承在实际开发中容易引发复杂性一般不推荐使用。多层继承虽然只能有一个直接父类但Java支持多层继承即一个类可以继承自另一个类而那个类又可以继承自其他类形成继承链。所有类最终都隐式继承自java.lang.Object类如果没有显式指定父类则默认继承Object。因此Object是所有类的根父类。五、子类到底能继承父类中的哪些内容内存原理深度解析理解了继承的基本概念和特点后我们来深入探讨一个核心问题子类究竟能从父类得到什么 这需要从内存角度理解。我们需要区分 “继承” 和 “访问权限” 两个概念继承指子类对象的内存空间中是否包含父类的成员。访问权限指能否使用这些成员方法或变量。我们这里结合继承所以谈论的是子类能否访问父类成员。两者的关系只有先继承了内存中存在才能讨论是否有权访问。总的来说成员类型非私有private构造方法❌ 不能继承❌不能继承成员变量✅ 能继承可访问✅ 能继承*不可直接访问成员方法✅ 能继承可访问/可重写❌ 不能继承也不可访问*注private成员变量会被继承存在于子类对象中但子类无法直接访问需要通过父类提供的公共getter/setter方法间接使用。5.1 构造方法的继承与访问5.1.1 继承为什么构造方法不能被继承在 Java 中构造函数的名字必须和当前类名完全一致。如果子类继承了父类的构造函数那么子类中就存在一个名字与父类名相同的方法这与 Java 语法冲突。5.1.2 访问子类如何访问父类的构造方法子类所有的构造方法中第一行默认隐式有一条super();语句用于调用父类的无参构造方法。这是为了确保在初始化子类对象之前先完成父类部分父类成员变量的初始化。可以显式调用父类的有参构造super(参数);但是这条语句也必须位于子类构造方法的第一行。 例如classFu{Stringname;publicFu(Stringname){// 有参构造无参构造不再默认提供this.namename;}}classZiextendsFu{publicZi(Stringname){super(name);// 必须显式调用父类有参构造}}this(…) 常用于构造器之间的调用例如在无参构造器中通过 this(默认值) 调用有参构造器从而复用初始化代码避免重复编写。而对于c和python可以利用默认形参5.2 成员变量的继承与访问5.2.1 继承无论父类的成员变量是公有还是私有子类对象都会继承父类成员变量在堆内存中都会为它们分配独立的内存空间。每个子类对象拥有自己的一份副本互不影响。这与static变量有着本质区别。classFu{Stringname;// 继承且可访问intage;// 继承且可访问}classZiextendsFu{Stringgame;// 子类自己的成员变量}publicclassTestStudent{publicstaticvoidmain(String[]args){ZiznewZi();// 堆中为Zi对象分配内存z.name铜门吹雪;// 操作的是自己对象中的name副本z.age23;// 操作自己对象中的age副本z.game王者农药;System.out.println(z.name, z.age, z.game);}}内存分配过程方法区加载 TestStudent.class、Fu.class、Zi.class存储类的结构信息如方法字节码、成员变量定义等。栈内存main 方法执行创建局部变量 z存储堆中对象的地址。堆内存执行 new Zi() 时JVM 在堆中为 Zi 对象分配内存。这块内存既包含子类自己定义的成员变量 game也包含从父类继承来的成员变量 name 和 age。它们按顺序连续排列每个变量都有自己独立的空间。继承之前我们提到的是抽取共性不是共享继承是“复制一份”到子类对象中而不是“共享”。每个子类对象都拥有自己独立的父类成员变量副本互不干扰。特性继承的成员变量static 变量内存分配每个子类对象独有一份其中父类成员变量和子类成员变量都是该对象独有的。所有对象共享同一份static 变量属于类所有对象共享同一份内存修改影响修改一个对象的变量不影响其他对象修改会影响所有对象形象比喻继承的成员变量父亲给每个孩子100元零花钱每个孩子都有自己的100元独立支配。static变量父亲给家里一个公共钱包里面放了100元每个孩子花里面的钱都会影响其他人。c与此一致私有成员变量也会继承只是访问不到5.2.2 访问我们可以看到因为权限控制私有成员不允许访问(但是依旧会继承父类的成员变量)而公有成员允许访问。5.2.2.1 就近原则与super/this访问当父类和子类出现同名成员访问同名成员时访问的是谁呢就近原则局部变量方法内定义 本类的成员变量 父类的成员变量publicclassFu{StringnameFu;}publicclassZiextendsFu{StringnameZi;publicvoidziShow(){StringnameziShow;System.out.println(name);// 输出 ziShow}}publicclassFu{StringnameFu;}publicclassZiextendsFu{StringnameZi;publicvoidziShow(){System.out.println(name);// 输出 Zi}}publicclassFu{StringnameFu;}publicclassZiextendsFu{publicvoidziShow(){System.out.println(name);// 输出 Fu}}使用this和super指定访问范围this.成员变量从本类开始找依据就近原则找不到就访问父类的成员变量。super.成员变量直接从父类部分开始找。详细讲解supersuper它的作用是告诉编译器在访问成员变量或方法时应从当前对象的父类部分开始查找。堆内存中的 Zi 对象 --------------------- | 对象头 (mark word) | --------------------- | Fu类的成员变量 | -- 这部分是“父类子对象” | (例如 Fu.name) | --------------------- | Zi类的成员变量 | -- 子类自己的成员 | (例如 Zi.name) | ---------------------super.name编译器从当前对象的父类部分起始位置开始查找因此访问的是 Fu.name。是子类对象中属于父类的那部分内存也就是子类从父类继承下来的成员变量副本。与其他子类对象无关更是不是访问“父类对象”的空间。注意当父类和子类成员变量没有重名时可以直接写变量名无需this或super。super只能指向直接父类无法通过super.super访问祖父类。5.3 成员方法的继承与访问5.3.1 继承虚方法表成员方法的继承与成员变量不同方法代码本身不会在每个对象中复制一份而是所有对象共享同一份方法代码。真正决定子类能调用哪些方法的是 “虚方法表”。虚方法表每个类在加载到方法区时都会生成一个虚方法表。虚方法表存储了该类所有虚方法非private、非final、非static的方法的实际入口地址指向方法区中的代码。子类的虚方法表会继承父类的虚方法表并将父类的虚方法地址复制过来同时添加自己特有的虚方法。如果子类重写了父类方法则虚方法表中该方法的地址会被替换为子类重写后的方法地址。所以当调用虚方法时JVM 通过对象的实际类型找到对应的虚方法表再根据方法索引快速定位到要执行的方法地址实现动态绑定。有没有联想到static的内存分析原理对于static成员变量共享一个内存地址而成员方法无论是否static都是只有一份static的区别在于有没有this关键字对于继承成员变量会被继承分配独立的内存空间成员方法只有一份真正决定继承的是虚方法表5.3.2 访问classFu{publicvoidfushow1(){...}privatevoidfushow2(){...}}classZiextendsFu{publicvoidzishow(){...}}publicclassTestStudent{publicstaticvoidmain(String[]args){ZiznewZi();z.zishow();z.fushow1();// z.fushow2(); // 编译错误private方法不可见}}同理只有子类从父类继承来的方法即子类对象内存中存在的方法我们才讨论它能否被访问在此基础上访问权限决定了是否允许直接使用。5.3.2.1 就近原则与方法重写方法重名的两种情况未重写仅仅是巧合同名如果子类定义了一个与父类方法签名相同但并非重写则调用时依然遵循就近原则。重写Override当子类觉得父类的方法实现不能满足自己的需求时可以在子类中重新定义该方法这就是重写。重写的规则方法名、参数列表必须相同。访问权限必须大于父类返回值类型必须小于父类建议就是我们尽量保持一致即可。使用override放在重写的方法上面校验子类重写是否正确。【即使不写也构成重写只不过我们强烈建议书写】私有方法、静态方法、final方法不能被重写。注释是给程序员看的注解是给程序员和虚拟机看的区别重载方法名一致参数列表不同返回值不做要求重写的本质覆盖当子类重写父类方法后子类对象的虚方法表中该方法的入口地址被替换为子类重写方法的地址。这意味着通过子类对象调用该方法时永远执行的是子类的重写版本父类的原方法被“覆盖”了。这时不存在“就近原则”的多个候选只有重写后的一个方法。无论是否是重写的方法我们可以看到super都是直接跳到父类。使用super关键字super.methodName()。在 Java 中是通过 invokespecial 指令实现的。这条指令在编译期就确定了目标方法即父类的那个具体方法并在类加载的解析阶段将其符号引用直接解析为方法区中该方法入口的直接引用可以理解为“地址”。运行时执行 invokespecial 时JVM 会直接跳转到那个地址执行代码完全不经过虚方法表也不参与动态分派。因此可以绕过重写直达父类。六、this 与 super 总结关键字访问成员变量访问成员方法访问构造方法是否可以做返回值本质thisthis.成员变量指代当前调用对象完整对象访问本类成员变量this.成员方法(..)访问本类成员方法this(...)访问本类构造方法可以当前对象的引用地址值supersuper.成员变量访问父类成员变量代表从父类开始查找成员不代表父类存储空间super.成员方法(..)访问父类成员方法super(...)访问父类构造方法不可以关键字指示从父类部分查找注意super不是一个引用不存储地址值。它只是一个编译器指令告诉编译器从当前对象的父类部分开始查找成员。因此它不能作为返回值返回。以上为个人学习总结旨在梳理个人理解。如有疏漏或不当之处欢迎指正与交流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价