黑马Java基础笔记-9
继承
-
Java中提供一个关键字
extends
,用这个关键字,我们可以让一个类和另一个类建立起继承关系。public class Student extends Person {}
- 继承关系:
Student
称为子类(派生类)Person
称为父类(基类/超类)
使用继承的好处
- 提高代码复用性 把多个子类中重复的代码抽取到父类中 (如:
Student
和Teacher
共有的name/age
属性) - 增强扩展性 子类可以在父类基础上新增功能 (如:
Student
类添加studentId
,Teacher
类添加teacherId
)
继承的特点
-
单继承限制
Java 只支持单继承(一个子类只能有一个直接父类),不支持多继承(c++)。 -
多层继承支持
- 允许链式继承:子类 A (直接继承)→ 父类 B (间接继承)→ 父类 C
- 最终所有类都直接或间接继承自
Object
类
-
Object 类地位
所有 Java 类的默认根父类(显式或隐式继承)public class A { // 默认继承Object类 // 类内容... }
-
子类只能直接访问父类中非私有的成员
若父类提供
public/protected
的get
和set
方法,子类可调用这些方法间接操作私有变量。
继承的内容
类型 | 非私有权限(public/protected/默认) | private权限 |
---|---|---|
构造方法 | 不能继承(需通过super() 调用) | 不能继承且无法调用 |
成员变量 | 能继承并直接访问 | 继承但不可直接访问(使用父类非私有的get/set) |
成员方法 | 能继承并直接调用 | 不能继承且不可访问 |
继承的内存图
当加载子类时,JVM会递归加载其父类链至Object
类,父类元数据(字段/方法定义)完整存储于方法区后,再加载子类。(先加载父类,再加载子类)
子类不能继承父类的构造方法,但是可以通过super调用
子类构造方法的第一行,有一个默认的super() ; (不写也存在,虚拟机会自动加上)
默认先访问父类中无参的构造方法,再执行自己。
如果想要方法文父类有参构造,必须手动书写。
super(name,age)//需要在父类加上这个有参构造
子类对象在堆中是单一内存块,按继承层级合并存储父类与子类属性。成员访问时,JVM优先从子类作用域查找,未找到则向上追溯父类。
| Fu.name | Fu.age | Zi.game | // 父类属性在前,子类新增在后
虚方法表
- 虚方法表属于类级别,存储在方法区(元数据区),所有同类对象共享同一虚方法表。
虚方法表的构建方法
1.父类虚方法表为基础
- 父类的虚方法表包含所有可继承方法(非private/static/final)的入口地址。
- 示例:父类
Animal
的虚方法表:
索引 | 方法名 | 地址 |
---|---|---|
0 | eat() | 0x1000 |
1 | sleep() | 0x2000 |
2.子类虚方法表构建步骤
- 步骤1:复制父类表:子类虚方法表初始化为父类表的完整拷贝。
- 步骤2:替换重写方法:若子类重写父类方法,替换对应索引位置的地址。
重写
- 1.重写方法的名称、形参列表必须与父类中的一致。
- 子类重写父类方法时,访问权限子类必须大于等于父类(暂时了解:空着不写<protected<public)
- 3.子类重写父类方法时,返回值类型子类必须小于等于父类(即子类方法的返回值类型可以是父类方法返回值类型的子类,并不是基本数据类型中的大小关系**)
- 4.建议:重写的方法尽量和父类保持一致。
- 5.只有被添加到虚方法表中的方法才能被重写
- 步骤3:追加新方法:子类新增的虚方法追加到表末尾。
- 示例:子类
Dog
的虚方法表:
索引 | 方法名 | 地址 | 说明 |
---|---|---|---|
0 | eat() | 0x3000 | ⬅️ 重写父类Animal.eat() |
1 | sleep() | 0x2000 | ⬅️ 继承父类Animal.sleep() |
2 | bark() | 0x4000 | ⬆️ 子类新增虚方法 |
多态
什么是多态?
对象的多种形态
多态的前提?
- 有继承/实现关系
- 有父类引入指向子类对象
- 有方法的重写
多态的好处?
使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
多态的内存图
核心结论
变量访问:静态绑定(编译期决定引用类型,偏移量由 JVM 在类加载阶段确定)
Animal a = new Dog();
a.name → 永远访问父类变量(Animal.name="动物")
-
规则:
编译看左边,运行也看左边
-
本质:编译器根据引用类型(Animal、Dog……)生成对“字段偏移量”(field offset)的访问指令;该偏移量并非在
javac
编译期硬编码,而是由 JVM 在类加载/链接‑准备阶段 根据继承链顺序、对象头大小与对齐策略动态计算并记录的。根据这个偏移量来访问这个对象(Dog)中对应引用类型(Animal)中的变量-
子类可访问继承链中所有非private变量(受访问修饰符限制,如 default 仅限同包)
-
但变量名冲突时优先访问最近父类的版本(如图中
name
变量的覆盖现象) -
若需访问子类自身字段,可通过
instanceof
+ 强制类型转换:
Animal a = new Dog(); if (a instanceof Dog) {Dog d = (Dog)a; // 改变引用类型d.name; // 访问子类层变量
-
方法调用:动态绑定(运行期决定)
a.show() → 执行子类重写方法(Dog.show())
- 规则:
编译看左边(检查语法),运行看右边(实际对象)
- 本质:通过对象头的类型指针
→
虚方法表(由于都是new Dog(),所以访问的都是访问在方法区中dog.class的虚方法表)动态路由
包
如果同时使用两个包中的同名类,需要用全类名
com.itheima.domain1.Teacher t = new com.itheima.domain1.Teacher();
com.itheima.domain2.Teacher t2 = newcom.itheima.domain2.Teacher();
final
- final修饰方法:最终方法,不能被重写
- final修饰类:最终类,不能被继承
- final修饰变量:是常量,不能被修改
- 基本数据类型:变量的值不能修改
- 引用数据类型:地址值不能修改,内部的属性值可以修改