java多态理解和底层实现原理剖析

news2024/11/24 12:26:22

java多态理解和底层实现原理剖析

  • 多态怎么理解
  • java中方法调用指令
    • invokespecial和invokevirtual指令的区别
    • invokeinterface指令
      • 方法表
        • 接口方法调用为什么不能利用方法表快速定位
  • 小结


多态怎么理解

抽象事务的多种具体表现,称为事务的多态性。我们在编码过程中通常都是面向接口,面向抽象编程,这其实就利用了多态的好处,帮我们屏蔽了多个子类之间的实现差异。


java中方法调用指令

我们知道c++中可以通过virtual来标注某个函数为虚函数,而在java中,除去静态函数,构造函数,私有函数,final函数,其他的函数都可以看做是虚函数,因为只有虚函数才具有多态性,才需要在运行时进行动态绑定。

Java中的方法大体分为两类: 实例方法和类(静态)方法。

  • 实例方法在被调用前,需要一个实例,而类(静态)方法不需要。
  • 实例方法使用动态绑定,而类方法使用静态绑定。

当java虚拟机调用一个类方法时,它会基于对象的引用类型来选择需要调用的方法。相反,当虚拟机调用一个实例方法时,它会基于对象的实际类型(运行时确定)来选择调用的方法。

对于类方法调用使用invokestaic指令,而实例方法调用使用invokevirtual指令完成:

操作码                    操作数
invokevirtual           实例对象引用(this对象)和方法参数--从调用栈栈中弹出,并为当前调用方法创建一个新的栈帧,然后压入新栈帧的局部变量表中,新栈帧压入虚拟机栈中,作为当前活动栈帧
invokestatic            方法参数  

对于构造函数,私有函数和super调用的函数,使用的是invokespecial进行调用:

操作码                    操作数
invokespecial           实例对象引用(this对象)和方法参数--实例对象引用(this对象)和方法参数--从调用栈栈中弹出,并为当前调用方法创建一个新的栈帧,然后压入新栈帧的局部变量表中,新栈帧压入虚拟机栈中,作为当前活动栈帧

对于类构造函数< client >而言,java虚拟机总是直接在类初始化时调用类初始化方法,并确保这个过程的是线程安全的,并不会对外提供任何字节码指令来调用类构造方法。

对于接口方法的调用,使用的是invokeinterface方法:

操作码                    操作数
invokeinterface           实例对象引用(this对象)和方法参数--实例对象引用(this对象)和方法参数--从调用栈栈中弹出,并为当前调用方法创建一个新的栈帧,然后压入新栈帧的局部变量表中,新栈帧压入虚拟机栈中,作为当前活动栈帧

invokespecial和invokevirtual指令的区别

invokespecial调用时,虚拟机将会按照引用类型来选择调用的方法。而invokevirtual指令执行时,会根据对象实际所属类型来选择调用哪一个方法。

可以简单的理解为虚拟机使用动态绑定来执行invokespecial指令,而对于invokevirtual指令来说,使用的是动态绑定。

invokespecial指令对于super方法的调用,会动态搜寻当前类的超类,找到离得最近的超类中该方法的实现,因此super方法调用是个例外,对于其他情况而言,都采用的是静态绑定。


invokeinterface指令

invokeinterface和invokervirtual指令功能相同: 它调用实例方法时使用动态绑定,这两个指令区别在于:

  • 当引用类型为类的时候,使用invokevirtual;
  • 当引用类型为接口的时候,使用invokeinterface;

除此之外,当执行invokevirtual指令调用实例方法时,由于符号引用都是懒解析的,所以第一次执行时,将实例方法的符号引用解析为直接引用,所生成的直接引用就是方法表中的一个偏移量,而且从此往后都可以使用同样的偏移量。

而对于invokeinterface指令而言,虚拟机每一次遇到invokeinterface指令,都需要重新搜寻一遍方法表,因为虚拟机不能假设这一次的偏移量与一次相同。

所以接口方法调用会比类方法调用更慢。


方法表

要讲方法表,我们先来简单回顾一下常量池解析过程,常量池解析的核心目的是将符号引用转换为直接引用,对于类型的直接引用可以是简单的指向保存类型数据的方法区中与实现相关的数据结构:

下面给出的是一个用go语言编写的Class数据结构,用于将class文件中类的静态结构映射为内存上类的动态数据结构

type Class struct {
	accessFlags       uint16
	name              string // thisClassName
	superClassName    string
	interfaceNames    []string
	constantPool      *ConstantPool
	fields            []*Field
	methods           []*Method
	sourceFile        string
	loader            *ClassLoader
	superClass        *Class
	interfaces        []*Class
	instanceSlotCount uint
	staticSlotCount   uint
	staticVars        Slots
	initStarted       bool
	jClass            *Object
}

类变量的直接引用可以指向方法区中保存的类变量的值:

    //注意上面Class类中的staticVars表示的就是类变量
	staticVars        Slots

类方法的直接引用可以指向方法区中一段数据结构:

 //我们完全也可以在Class中再给出一个staticMethods属性,用于指向方法区中类方法元数据信息,同时用于和实例方法区分开来
 staticMehthods []*Method

指向实例变量和实例方法的直接引用都是偏移量:

    //Class中存储实例变量和实例方法元数据信息
	fields            []*Field
	methods           []*Method

这里的关键点在于实例变量和实例方法在数组中的占据的索引位置是不变的 ,例如: 子类继承了某个父类,子类自己的方法表中也是父类方法优先,接着是自己的方法,这样可以确保父类方法在子类和父类方法表中的索引都是一致的。

实例变量同理,例如: CockerSpaniel继承了父类Dog,父类提供了一个wagCount的实例字段,可以看到此时wagCount在父类Dog和子类CockerSpaniel实例字段表中的索引都是1,是一致的:
在这里插入图片描述
父类的实例变量优先被存储到子类的实例变量表前部,并且每一个类的实例变量出现顺序和他们在class文件中的出现顺序是一致的。

超类的方法出现在来自子类的方法前,并且方法表中方法指针排序顺序和方法在class文件中出现顺序相同,当然,如果存在子类覆盖父类方法的情况,那么子类覆盖的方法会出现在超类中该方法第一次出现的位置。

方法表中只会存储非私有的实例方法,静态方法不会出现在这里,因为他们是静态绑定的,不需要在方法表的间接指向。私有方法和实例的初始化方法也不需要在这里出现,因为他们也是静态绑定的。只有invokevirtual和invokeinterface指令调用的方法才需要出现在这个方法表中。


下面给出参考书上的一个简单案例:
在这里插入图片描述
Dog覆写了Object的toString方法,覆写的方法出现的索引还是和父类toString方法出现的位置保持一致,并且Dog类自己实现的SayHello方法排在方法表末尾。


接口方法调用为什么不能利用方法表快速定位

当通过接口引用来访问实例方法时,符合引用被解析为直接引,但是直接引用不能保证得到方法表的偏移量,因为无法保证是子类自己实现了接口还是超类实现的接口,那么接口方法在方法表中的出现顺序就无法被确定下来。

因此,不管何时java虚拟机从接口引用调用一个方法,它必须搜索对象的类的方法表来找到一个合适的方法,这种调用接口引用的实例方法会比类引用上调用实例方法慢很多。

当然,虚拟机通常会采取优化手段来加速接口方法调用执行过程,例如: 缓存第一次查找得到的方法索引等,但是,总的来说,接口方法调用还是会比类实例方法调用慢很多。


小结

java中多态是通过动态绑定实现的,动态绑定是通过invokeVirtual指令和invokeInterface指令实现,这两条指令执行时,都会根据当前实际调用对象类型去查找方法,区别在于invokeVirtual可以通过方法偏移量快速在方法表中定位方法,而invokeInterface则需要每次扫描方法表进行寻找。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络 第4章 作业1

一、选择题 1. 由网络层负责差错控制与流量控制,使分组按序被递交的传输方式是_________&#xff08;C&#xff09; A&#xff0e;电路交换 B&#xff0e;报文交换 C&#xff0e;基于虚电路的分组交换 D&#xff0e;基于数据报的分组交换 2. TCP/IP 参考…

Bunifu.UI.WinForms 6.0.2 Crack

Bunifu.UI.WinForms为 WinForms创建令人惊叹的UI Bunifu.UI.WinForms我们为您提供了现代化的快速用户界面控件。用于 WinForms C# 和 VB.NET 应用程序开发的完美 UI 工具 简单 Bunifu.UI.WinForms没有臃肿的特征。正是您构建令人惊叹的 WinForms 应用程序所需要的。只需拖放然…

计算机网络高频知识点

目录 一、http状态码 二、强缓存与协商缓存 三、简单请求与复杂请求 四、PUT 请求类型 五、GET请求类型 六、GET 和 POST 的区别 七、跨域 1、什么时候会跨域 2、解决方式 八、计算机网络的七层协议与五层协议分别指的是什么 1、七层协议 2、五层协议 九、计算机网…

监控生产环境中的机器学习模型

简介 一旦您将机器学习模型部署到生产环境中&#xff0c;很快就会发现工作还没有结束。 在许多方面&#xff0c;旅程才刚刚开始。你怎么知道你的模型的行为是否符合你的预期&#xff1f;下周/月/年&#xff0c;当客户&#xff08;或欺诈者&#xff09;行为发生变化并且您的训练…

服务器部署—部署springboot之Linux服务器安装jdk和tomcat【建议收藏】

我是用的xshell连接的云服务器&#xff0c;今天想在服务器上面部署一个前后端分离【springbootvue】项目&#xff0c;打开我的云服务器才发现&#xff0c;过期了&#xff0c;然后又买了一个&#xff0c;里面环境啥都没有&#xff0c;正好出一期教程&#xff0c;方便大家也方便自…

大数据框架之Hadoop:MapReduce(三)MapReduce框架原理——ReduceTask工作机制

1、ReduceTask工作机制 ReduceTask工作机制&#xff0c;如下图所示。 &#xff08;1&#xff09;Copy阶段&#xff1a;ReduceTask从各个MapTask上远程拷贝一片数据&#xff0c;并针对某一片数据&#xff0c;如果其大小超过一定阈值&#xff0c;则写到磁盘上&#xff0c;否则直…

DHTMLX Suite 8.0.0 Crack

适用于现代 Web 应用程序的强大 JavaScript 小部件库 - DHTMLX 套件 用于创建现代用户界面的轻量级、快速且通用的 JavaScript/HTML5 UI 小部件库。 DHTMLX Suite 有助于推进 Web 开发和构建具有丰富功能的数据密集型应用程序。 DHTMLX Suite 是一个 UI 小部件库&#xff0c;用…

指针数组和数组指针的区别

数组指针&#xff08;也称行指针&#xff09;定义 int (*p)[n];()优先级高&#xff0c;首先说明p是一个指针&#xff0c;指向一个整型的一维数组&#xff0c;这个一维数组的长度是n&#xff0c;也可以说是p的步长。也就是说执行p1时&#xff0c;p要跨过n个整型数据的长度。如要…

【前端】JavaScript构造函数

文章目录概念执行过程返回值原型与constructor继承方式原型链其他继承方式&#xff08;还没写&#xff09;参考概念 在JS中&#xff0c;通过new来实例化对象的函数叫构造函数。实例化对象&#xff0c;也就是初始化一个实例对象。构造函数一般首字母大写。 构造函数的目的&…

Android性能调优 - 启动优化

一、APP启动优化1、 你对 APP 的启动有过研究吗? 有做过相关的启动优化吗?程序员&#xff1a;之前做项目的时候&#xff0c;我发现程序在冷启动时&#xff0c;会有 1s 左右的白屏闪现&#xff0c;低版本是黑屏的现象&#xff0c;在这期间我通过翻阅系统主题源码&#xff0c;发…

26 openEuler管理网络-使用ip命令配置网络

文章目录26 openEuler管理网络-使用ip命令配置网络26.1 配置IP地址26.1.1 配置静态地址26.1.2 配置多个地址26.2 配置静态路由26 openEuler管理网络-使用ip命令配置网络 说明&#xff1a; 使用ip命令配置的网络配置可以立即生效但系统重启后配置会丢失。 26.1 配置IP地址 使用…

JVM - G1垃圾收集器深入剖析

​​​​​​​1、G1收集器概述 HotSpot团队一直努力朝着高效收集、减少停顿(STW: Stop The World)的方向努力&#xff0c;也贡献了从串行Serial收集器、到并行收集器Parallerl收集器&#xff0c;再到CMS并发收集器&#xff0c;乃至如今的G1在内的一系列优秀的垃圾收集器。 G…

ER图、ERD图

ER图、ERD图1. 什么是ERD1.1 举例2. ERD符号指南2.1 实体2.2 属性2.3 主键2.4 外键2.4 关系2.5 基数2.5.1 一对一的基数的例子2.5.2 一对多的基数的例子2.5.3 多对多的基数的例子3.概念、逻辑和物理数据模型3.1 概念数据模型3.2 逻辑数据模型3.3 物理数据模型4.如何绘制ER图?5…

python的装饰器与设计模式中的装饰器模式

相信很多人在初次接触python中的装饰器时&#xff0c;会跟我一样有个疑问&#xff0c;这跟设计模式中的装饰器模式有什么区别吗&#xff1f;本质上是一样的&#xff0c;都是对现有对象&#xff0c;包括函数或者类的一种扩展。这篇文档将进行对比分析。 python的装饰器 装饰器…

Acwing 蓝桥杯 第一章 递归与递推

我上周在干什么&#xff0c;感觉我上周啥也没训&#xff0c;本来两天一次的vp也没v很寄啊&#xff0c;再这样下去真不行了先总结一下如何爆搜&#xff1a;先去确定好枚举的对象枚举的对象很重要&#xff01;&#xff01;这直接影响了复杂度然后就是去想递归树就好了一、确定状态…

基于VSG的预同步并离网控制MATLAB仿真模型

MATLAB2019b主要模块&#xff1a;并网逆变器VSG控制预同步控制电流电流双环控制锁相环、三相准PR控制、PWM0.65秒开始并网运行&#xff01;&#xff01;&#xff01;仿真模型&#xff1a;逆变器输出电压、电流波形。电压为单相&#xff08;可观察相位情况&#xff09;&#xff…

【逐步剖C】-第八章-指针进阶-下

前言&#xff1a;在文章【逐步剖C】-第八章-指针进阶-上与指针初阶中我们介绍了有关指针较为全面的知识&#xff0c;本篇文章主要从指针和数组相关试题出发&#xff0c;进一步巩固对指针的学习。接下来&#xff0c;让我们开始吧。 一、“真假”数组名 前言&#xff1a;这一部…

【每日一题】集合汇总 集合面试题

集合前言&#xff1a;图片一、集合分类1、实现 Collection 接口2、实现 Map 接口二、实现类定义1、ArrayList&#xff08;非线程安全&#xff09;2、LinkedList&#xff08;非线程安全&#xff09;3、HashSet&#xff08;非线程安全&#xff09;4、TreeSet&#xff08;非线程安…

Linux学习(8.6)文件与目录的默认权限与隐藏权限

目录 文件与目录的默认权限与隐藏权限 文件的默认权限&#xff1a;umask chattr (配置文件隐藏属性) lsattr (显示文件隐藏属性) 文件特殊权限&#xff1a; SUID, SGID, SBIT 观察文件类型&#xff1a;file 以下内容转载自鸟哥的Linux私房菜 文件与目录的默认权限与隐藏权…

比特数据结构与算法(第四章_中_续①)堆排序(详解)

本篇讲讲八大排序之一的&#xff1a;堆排序 概念复习&#xff1a;比特数据结构与算法&#xff08;第四章_上&#xff09;树和二叉树和堆的概念及结构_GR C的博客-CSDN博客一、堆排序的概念堆排序&#xff08;Heapsort&#xff09;&#xff1a;利用堆积树&#xff08;堆&#xf…