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

【Java】继承和多态在 Java 中是怎样实现的?

extends 关键字

class 子类 extends 父类 {...
} // 类继承是单继承

父类的哪些成员被继承 ?

访问修饰符 public 和 protected 修饰的父类成员字段和成员方法可以被继承 , 父类的默认方法只能在同包下继承 , 父类的 private 成员和构造方法不可继承 .

super 关键字

表示父类引用 , 用于在子类中访问父类的构造方法 , 成员字段 和 成员方法 .

super 调用父类构造方法

super() 调用父类的构造方法必须在子类构造方法的第一行 , 有参构造 super(E e) 同理 , 如果没有显式调用则在子类所有构造方法的第一行隐式插入 super() 父类的无参构造 .

**为什么 ? **

类加载器是沿继承链自上而下初始化类的 , 先初始化父类后初始化子类 , 实例化创建子类时同理 , 先在内存中创建一个对象 , 先调用父类构造再调用子类构造 , 如果子类执行到一半才调用父类构造 , 那么在执行期间依赖未定义的父类字段容易造成未定义行为 .

super 调用父类成员字段

变量隐藏机制

子类如果有成员字段与父类的成员字段同名 , 那么父类的字段会被隐藏 , 区别于方法重写 , 因为 Java 字段访问是编译时的静态绑定 , 所以可以通过向上转型访问父类变量 .

在类中可以通过 super + . 调用父类成员字段 ( 不论是否被隐藏 ) , 如果子类没有隐藏父类的成员字段 a , 那么 super.athis.a 完全等价 .

super 调用父类成员方法

不论是否被重写 , super 总是调用父类的成员方法 .

转型机制

向上转型

将父类赋给子类变量 , 改变子类实例在栈中的对象类型 , 此时调用的是父类的成员字段和子类的重写方法 .

向下转型 ( 强转 )

将父类强制转换成子类 , 改变父类实例在栈中的对象类型 , 此时调用的是子类字段和子类的重写方法 , 我们通常使用默认转型创建父类实例 , 这样堆中会有子类成员字段的内存空间 , 此时再强转成子类是相对安全的 , 因为内存中有子类字段的空间 , 可以正常访问 .

要注意的是 , 将父类实例向下转型成子类实例不会调用子类的构造方法对子类成员字段进行初始化 , 如果直接创建父类实例再进行强转 , 此时调用子类的成员字段是极其危险的 , 会报错 . 只有有继承关系的类之间才能进行强转 , 否则也会报错 . 总结就是 , 语法上允许强转 , 但访问未初始化的子类成员字段会报错 .

静态绑定和动态绑定

Class Metadata 类元数据

类的字节码文件在类加载器加载后会有对应的类元数据 , 其中有很多类信息 , 包含 父类引用 , 字段表 , 虚函数表 vtable 等 .

父类引用

类元数据只记录父类信息 , 包括父类的常量池索引 , 其指向父类的全限定名 .

JVM 通过递归加载类字节码文件 , 形成完整的继承链 .

字段表

类元数据只会记录子类的成员字段信息 , 在创建子类实例调用完所有构造方法后 , JVM 会查表将父类字段和子类字段都放入堆中 , 所以在堆中父类字段和子类字段是连续的 , 父类字段在前 .

偏移地址

偏移地址就是堆中字段相对于对象头的偏移量 .

class A {int x = 10;
}
class B extends A {int x = 20;
}
public static void main(String[] args) {A a = new B;
}

new 语句调用后 , 堆中内存是这样的 :

[对象头][10][20]

如果对象头占 8 字节 , 那么 A 类的 x 字段的偏移地址是 +8 , B 类的 x 字段的偏移地址是 + 12 .

虚函数表

虚方法 : 非静态 , 非 private 修饰 , 非构造方法的所有方法 .

虚函数表本质是方法指针数组 , 存储类中所有虚方法的地址 .

子类继承父类时会复制父类的虚函数表 , 并用重写方法替换同名父类方法 , 向下转型和向下转型时 , 虚函数表不变 .

非虚方法在编译时就确定了 , 字节码文件中存在定位指令 , JVM 可以直接调用 .

A a = new B;

语句中 A 是 a 的引用数据类型 , 存储在栈中 ; B 是 a 的实际数据类型 , 存储在堆中 (实际是不一定在这里 , 只是区别一下 ) .

JVM 按照堆中实例 a 的实际数据类型查虚函数表 ; 按照创建实例时的偏移地址查字段值 .

重载方法

重载方法是静态绑定的 , 虚函数表在生成时是按照 方法名 + 参数信息 ( 方法签名 ) 的信息得到的各方法地址的指针 , 重载方法是不同的方法 , 所以如果子类重写父类重载方法的其中之一 , 在子类生成虚函数表时替换的是该重载方法的指针 , 子类再调用父类其他的重载方法时仍然调用的是父类方法 .

总结

针对 new 语句的左右两侧 , 左侧是该实例的引用数据类型 , 右侧是该实例的实际数据类型 , 按照引用数据类型调用类的成员是静态绑定 , 因为引用数据类型在编译期确定 ; 按照实际数据类型调用类的成员是动态绑定 , 因为实际数据类型在运行时确定 .

接口 interface 和实现 implements

接口是一种抽象类型 , 用于定义类应该实现的方法 , 而不提供具体实现 .

  1. 接口只提供方法签名 .
  2. 接口不能实例化 , 自然没有构造方法 .
  3. 实现类与接口之间是多继承 , 实现类可以实现多个接口 .
  4. 接口同样支持向上转型和动态绑定 .
  5. 接口的成员字段只能是静态常量 , 字段是被 public static final 隐式修饰的 ( 显式写出等价于没写 ) , 必须有初始值 .

关于接口的默认方法和抽象方法 ( Java 8 )

如果继承的多个接口存在同名抽象方法或同名默认方法 , 则其在实现类中重写时只需要一次 .

如果继承的多个接口的抽象方法和默认方法同名 , 则在实现类中必须重写 , 不能使用默认方法接口提供的关于该方法的默认实现 .

如果希望在实现类中调用接口的默认方法 , 则必须显式通过 接口名 + super + 方法名 调用 .

super 是用来调用父类方法和字段的 , 而不是接口 .


接口的成员字段

接口的字段只能是常量字段 , 静态字段属于接口 , 但实现类和实现类实例可以调用这个常量字段 , 实现类中可以用 接口名 + 字段名 调用或 直接使用字段名调用 , 同时在 main 方法中可以用实现类的实例名调用 , 也可以用实现类名调用 , 更可以用接口名调用 , 不过不推荐用实现类及其实例调用 , 这样代码不够清晰 , 也会有冲突问题 :

如果实现类实现的多个接口有同名字段的话 , 就不能直接使用字段名调用或者在 main 方法中使用实例名或实现类名调用了 , 否则会报错 .

实现类能够调用接口字段并非继承 , 字段仍然属于接口 , 在字节码文件中会统一优化成 接口名 + 字段名 的形式 , 前提是让编译器知道调用的是哪个接口的字段 .

抽象类

抽象类的继承是单继承 , 可以包含普通方法和任意访问修饰符修饰的成员字段 .

抽象类同样无法实例化 , 但有构造方法 , 在子类构造时被调用 .

默认方法冲突

如果子类继承父类的同时作为实现类实现接口 , 父类中有具体实例方法 , 接口存在同名的默认方法 , 子类在调用该方法时会优先调用父类方法 , 即使父类是抽象类 .

如果实现类实现多个接口 , 接口存在同名默认方法 , 则实现类必须重写该方法 , 这样是为了实现类实例可以直接调用接口默认方法 .

接口继承接口 多继承

子接口继承父接口的所有抽象方法和默认方法 , 子接口可以声明新的抽象方法 .

如果子接口重写了父接口的默认方法 , 则使用子接口版本 .

子接口仍然是接口 , 不能重写父接口的抽象方法 .

子接口的同名默认方法冲突仍然是强制重写 .

http://www.xdnf.cn/news/548083.html

相关文章:

  • Token的组成详解:解密数字身份凭证的构造艺术
  • AI与产品架构设计(6):Agent与知识图谱结合在云服务与通用场景中的应用实践
  • 深入探索百度智能云千帆AppBuilder:从零开始构建AI应用
  • 在 Kotlin 中,什么是内联函数?有什么作用?
  • 基于Java的校运会管理系统【附源码】
  • MCP专题 | 探索MCP服务器世界:增强AI能力的精选推荐
  • 奥威BI:打破AI数据分析伪场景,赋能企业真实决策价值
  • 在 JavaScript 中正确使用 Elasticsearch,第二部分
  • 新书速览|GraphPad Prism图表可视化与统计数据分析:视频教学版
  • idea部署本地仓库和连接放送远程仓库
  • 关于 Web 漏洞原理与利用:3. CSRF(跨站请求伪造)
  • 告别格式不兼容!画质无损 RainCrack 免费无广告转码软件
  • 【C++】vector模拟实现
  • 钉钉手机端应用访问提示: 钉钉授权码获取遇到了 “签名校验失败“ 的错误,钉钉开发文档有坑造成的
  • 青少年编程与数学 02-019 Rust 编程基础 19课题、项目发布
  • 医学影像辅助诊断系统开发教程-基于tensorflow实现
  • CVE-2022-22978源码分析与漏洞复现
  • 实用 Git 学习工具推荐:Learn Git Branching
  • Mybatis的逆向工程Generator
  • 销售易史彦泽:从效率工具到增长引擎,AI加速CRM不断进化
  • SQL次日留存率计算精讲:自连接与多字段去重的深度应用
  • OpenCV 图像色彩空间转换
  • Yersinia:layer 2攻击框架!全参数详细教程!Kali Linux教程!
  • jieba分词
  • PCB设计教程【入门篇】——电路分析基础-基本元件(二极管三极管场效应管)
  • 可视化图解算法42:寻找峰值
  • Cribl 中 Parser 扮演着重要的角色 + 例子
  • 鸿蒙HarmonyOS多设备流转:分布式的智能协同技术介绍
  • RustDesk CentOS自建中继节点
  • Linux 特权管理与安全——从启用 Root、Sudo 提权到禁用与防护的全景解析