Java多态实现原理

Java多态实现原理##前言多态是Java语言重要的特性之一,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。Java对于方法调用动态绑定的实现主要依赖于方法表,但通过引用调用(invokevitual)和接口引用调用(invokeinterface)的实现则有所不同。Java多态实现原理的大致过程:首先是Java编译器将Java源代码编译成class文件。在编译过程中,会根据静态类型将调用的符号引用写到class文件中。在执行时,JVM根据class文件找到调用方法的符号引用,然后在静态类型的方

大家好,又见面了,我是你们的朋友全栈君。

##前言
多态是Java语言重要的特性之一,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。Java对于方法调用动态绑定的实现主要依赖于方法表,但通过引用调用(invokevitual)和接口引用调用(invokeinterface)的实现则有所不同。

Java多态实现原理的大致过程:首先是Java编译器将Java源代码编译成class文件。在编译过程中,会根据静态类型将调用的符号引用写到class文件中。在执行时,JVM根据class文件找到调用方法的符号引用,然后在静态类型的方法表中找到偏移量,然后再根据this指针确定对象的实际类型,使用实际类型的方法表(偏移量跟静态类型中的偏移量一样是指 就是用的静态类型中的偏移量,因为符号引用在静态类型的方法表中找到的偏移量是同一个),如果在实际的方法中找到该方法(说明参数值对上了)则直接调用,否则认为没有重写父类的方法则按照继承关系从下往上搜索来调用方法。

Java多态实现原理 image

Java多态实现原理

程序运行时,需要某个类是,类载入系统会将相应的class文件载入到JVM中,并在内部建立该类的 类型信息 (这个类型信息其实就是class文件在JVM中存储的一种数据结构),包含java类定义的所有信息(方法代码、类和成员变量、以及实现动态调用的核心 – 方法表 )。这个类型信息存储在方法区。

注意:这个方法去中的类型信息跟在堆中存放的class对象是不同的。在方法区中,这个class的类型信息只有唯一的实例(所以是各个线程共享的内存区域),而在堆中可以有多个该class对象。可以通过堆中的class对象访问到方法去中的类型信息(像Java的反射机制,通过class对象可以访问到该类的所有信息)。

【重点】

方法表是实现动态调用的核心。上面讲过方法表存放在方法区中的类型信息中。为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向一个记录该类方法的方法表,方法表中的每一个项都是对应方法的指针。
这些方法中包括从父类继承的所有方法以及自身重写(override)的方法。

【拓展】

方法区:方法区和JAVA堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 
运行时常量池:它是方法区的一部分,Class文件中除了有类的版本、方法、字段等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分信息在类加载时进入方法区的运行时常量池中。 
方法区的内存回收目标是针对常量池的回收及对类型的卸载。

#####Java 的方法调用方式

Java 的方法调用有两类,动态方法调用与静态方法调用。

  • 静态方法调用是指对于类的静态方法的调用方式,是静态绑定的
  • 动态方法调用需要有方法调用所作用的对象,是动态绑定的。

类调用 (invokestatic) 是在编译时就已经确定好具体调用方法的情况。

实例调用 (invokevirtual)则是在调用的时候才确定具体的调用方法,这就是动态绑定,也是多态要解决的核心问题。

JVM 的方法调用指令有四个,分别是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前两个是静态绑定,后两个是动态绑定的。本文也可以说是对于JVM后两种调用实现的考察。

方法表与方法调用

如有类定义 Person, Girl, Boy

class Person {
    public String toString() {
        return "I'm a person.";
    }
    public void eat() {
    }
    public void speak() {
    }
}

class Boy extends Person {
    public String toString() {
        return "I'm a boy";
    }
    public void speak() {
    }
    public void fight() {
    }
}

class Girl extends Person {
    public String toString() {
        return "I'm a girl";
    }
    public void speak() {
    }
    public void sing() {
    }
}

当这三个类被载入到 Java 虚拟机之后,方法区中就包含了各自的类的信息。Girl 和 Boy 在方法区中的方法表可表示如下:

Java多态实现原理

可以看到,Girl 和 Boy 的方法表包含继承自 Object 的方法,继承自直接父类 Person 的方法及各自新定义的方法。注意方法表条目指向的具体的方法地址,如 Girl 继承自 Object 的方法中,只有 toString() 指向自己的实现(Girl 的方法代码),其余皆指向 Object 的方法代码;其继承自于 Person 的方法 eat() 和 speak() 分别指向 Person 的方法实现和本身的实现。

如果子类改写了父类的方法,那么子类和父类的那些同名的方法共享一个方法表项。

因此,方法表的偏移量总是固定的。所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值。
Person 或 Object中的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。

如调用如下:

class Party {
    void happyHour() {
        Person girl = new Girl();
        girl.speak();
    }
}

当编译 Party 类的时候,生成 girl.speak()的方法调用假设为:    Invokevirtual #12

设该调用代码对应着 girl.speak(); #12 是 Party 类的常量池的索引。JVM 执行该调用指令的过程如下所示:

Java多态实现原理

(这里有个错误,上图为ClassReference常量池而非Party的常量池)
【再次拓展】

常量池在逻辑上可以分成多个表,每个表包含一类的常量信息,本文只探讨对于 Java 调用相关的常量池表。

CONSTATNT_Method_info**:**类方法引用表;包含引用的任何类型方法的描述信息,主要包括类信息索引和名字类型索引。

CONSTATNT_Class_info**:**类信息表;包含任何被引用的类或接口的 ‘符号引用’ ,每一个条目主要包含一个索引,指向CONSTA_Utf8_info表,表示该类或接口的全限定名。

CONSTATNT_NameAndType_info:名字类型表;包含引用的任意方法或字段的名称和描述符信息在字符串常量中的索引。

CONSTATNT_Utf8_info:字符串常量表; 该表包含该类所使用的所有字符串常量,比如代码中的字符串引用、引用的类名、方法的名字、其他引用的类与方法的字符串描述等等。其余常量池表中所涉及到的任何常量字符串都被索引至该表。

可以看到,给定任意一个方法的索引,在常量池中找到对应的条目后,可以得到该方法的类索引(classindex)和名字类型索引 (nameandtypeindex), 进而得到该方法所属的类型信息和名称及描述符信息(参数,返回值等)——从而通过对方法的类型信息和名称及描述符信息(参数,返回值等)来确定具体是调用哪一个方法。

JVM执行  Invokevirtual #12 指令的过程:

(1)在常量池中找到方法调用的符号引用。 JVM 首先查看 Party(应为ClassReference常量池) 的常量池索引为 12 的条目 (此条目即指 – 查看常量池中的CONSTATNT_Method_info表,即类方法引用表),再 进一步查看常量池中的(CONSTANTClassinfo,CONSTANTNameAndTypeinfo ,CONSTANTUtf8info) 三个表。

(2) 可得出要调用的方法是 Person 的 speak 方法, 查看 Person 的方法表,得出 speak 方法在该方法表中的偏移量 15,这就是该方法调用的直接引用。

(3) 根据this指针得到具体的对象(即girl所指向位与堆中的对象)

(4)根据对象得到该对象对应的方法表,根据偏移量15查看有无重写(override)该方法,如果重写,则可以直接调用(Girl的方法表的speak项指向自身的方法而非父类);如果没有重写,则需要拿到按照继承关系从下往上的基类(这里是Person类)的方法表,同样按照这个偏移量15查看有无该方法。
##最后
以上,是对Java多态实现原理翻阅两篇博文后为便于理解而整理而出。
参考博文:
https://www.cnblogs.com/kaleidoscope/p/9790766.html
https://zhuanlan.zhihu.com/p/94086109
大家看完有什么不懂的可以在下方留言讨论.
谢谢你的观看。

参考博文:
https://www.cnblogs.com/kaleidoscope/p/9790766.html
https://zhuanlan.zhihu.com/p/94086109
大家看完有什么不懂的可以在下方留言讨论.
谢谢你的观看。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/156620.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • useGeneratedKeys和keyProperty

    useGeneratedKeys和keyProperty<!–useGeneratedKeys:仅适用于insert和update)这会令MyBatis使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键(比如:像MySQL和SQLServer这样的关系型数据库管理系统的自动递增字段),默认值:false。说白了就是使用自增主键,增加的时候自动使用自增主键–><!–keyProperty:使用主键自增之后,就无法拿到主键,但是需要使

  • JAVA常用数据结构及原理分析(面试总结)「建议收藏」

    JAVA常用数据结构及原理分析(面试总结)「建议收藏」最近准备面试,因此整理一份Java中常用的数据结构资料,方便面试;java.util包中三个重要的接口及特点:List(列表)、Set(保证集合中元素唯一)、Map(维护多个key-value键值对,保证key唯一)。其不同子类的实现各有差异,如是否同步(线程安全)、是否有序。常用类继承树:以下结合源码讲解常用类实现原理及相互之间的差异。Collection(所有…

  • 北风设计模式课程—9、原型模式的作用和意义[通俗易懂]

    北风设计模式课程—9、原型模式的作用和意义[通俗易懂]北风设计模式课程—9、原型模式的作用和意义

  • 用flash做古诗动画_Flash制作跟我学 用遮罩技术制作古诗动画-FLASH课件制作(FLASH课件制作教程)-flash课件吧(湖北金鹰)…

    用flash做古诗动画_Flash制作跟我学 用遮罩技术制作古诗动画-FLASH课件制作(FLASH课件制作教程)-flash课件吧(湖北金鹰)…Flash制作跟我学用遮罩技术制作古诗动画提示:遮罩—使用遮罩图层可以让Flash设计者有选择地显示图层的某些部分,应用遮罩需要创建一个图层成为遮罩的图层,而它下面的图层即成为被遮盖的图层。1准备一幅配合诗词意境的图片作为背景(古色古香的水墨画当属首选),根据图片的尺寸设计Flash文件的大小(本文创建尺寸为333×438像素,背景为蓝色)。打开“文件”菜单,选择“导入”→“导入到舞台”命令把背…

  • C#开源资源大汇总

    C#开源资源大汇总

  • 详解C语言中的数组指针与指针数组

    详解C语言中的数组指针与指针数组·详解数组指针与指针数组·数组指针一、区分首先我们需要了解什么是数组指针以及什么是指针数组,如下图:int*p[5];int(*p)[5];数组指针的意思即为通过指针引用数组,p先和*结合,说明了p是一个指针变量,指向一个大小为5的数组。所以,int(*p)[5]即为一个数组指针。int*p[5]则是一个大小为5且存放整型指针的数组。二、数组元素的指针1.定…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号