精华内容
下载资源
问答
  • 多态实现原理
    2021-03-13 22:52:08

    多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定。这是如何实现的呢?

    请看下面的程序,该程序演示了多态类对象存储空间的大小。

    
    #include <iostream>
    using namespace std;
    class A
    {
    public:
    int i;
    virtual void func() {}
    virtual void func2() {}
    };
    class B : public A
    {
    int j;
    void func() {}
    };
    int main()
    {
    cout << sizeof(A) << ", " << sizeof(B); //输出 8,12
    return 0;
    }

    在 32 位编译模式下,程序的运行结果是:
    8, 12

    如果将程序中的 virtual 关键字去掉,输出结果变为:
    4, 8

    对比发现,有了虚函数以后,对象所占用的存储空间比没有虚函数时多了 4 个字节。实际上,任何有虚函数的类及其派生类的对象都包含这多出来的 4 个字节,这 4 个字节就是实现多态的关键——它位于对象存储空间的最前端,其中存放的是虚函数表的地址。

    每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着该虚函数表的指针(可以认为这是由编译器自动添加到构造函数中的指令完成的)。

    虚函数表是编译器生成的,程序运行时被载入内存。一个类的虚函数表中列出了该类的全部虚函数地址。例如,在上面的程序中,类 A 对象的存储空间以及虚函数表(假定类 A 还有其他虚函数)如图 1 所示。
     


    图1:类A对象的存储空间以及虚函数表


    类 B 对象的存储空间以及虚函数表(假定类 B 还有其他虚函数)如图 2 所示。
     


    图2:类B对象的存储空间以及虚函数表


    多态的函数调用语句被编译成根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的一系列指令。

    假设 pa 的类型是 A*,则 pa->func() 这条语句的执行过程如下:

    1) 取出 pa 指针所指位置的前 4 个字节,即对象所属的类的虚函数表的地址(在 64 位编译模式下,由于指针占 8 个字节,所以要取出 8 个字节)。如果 pa 指向的是类 A 的对象,则这个地址就是类 A 的虚函数表的地址;如果 pa 指向的是类 B 的对象,则这个地址就是类 B 的虚函数表的地址。

    2) 根据虚函数表的地址找到虚函数表,在其中查找要调用的虚函数的地址。不妨认为虚函数表是以函数名作为索引来查找的,虽然还有更高效的查找方法。

    如果 pa 指向的是类 A 的对象,自然就会在类 A 的虚函数表中查出 A::func 的地址;如果 pa 指向的是类 B 的对象,就会在类 B 的虚函数表中查出 B::func 的地址。

    类 B 没有自己的 func2 函数,因此在类 B 的虚函数表中保存的是 A::func2 的地址,这样,即便 pa 指向类 B 的对象,pa->func2();这条语句在执行过程中也能在类 B 的虚函数表中找到 A::func2 的地址。

    3) 根据找到的虚函数的地址调用虚函数。

    由以上过程可以看出,只要是通过基类指针或基类引用调用虚函数的语句,就一定是多态的,也一定会执行上面的查表过程,哪怕这个虚函数仅在基类中有,在派生类中没有。

    多态机制能够提高程序的开发效率,但是也增加了程序运行时的开销。虚函数表、各个对象中包含的 4 个字节的虚函数表的地址都是空间上的额外开销;而查虚函数表的过程则是时间上的额外开销。

    在计算机发展的早期,计算机非常昂贵稀有,运行速度慢,计算机的运算时间和内存是宝贵的,因此人们不惜多花人力编写运行速度更快、更节省内存的程序;如今,计算机的运算时间和内存往往没有人的时间宝贵,运算速度也很快,因此,在用户可以接受的前提下,降低程序运行的效率以提升人员的开发效率就是值得的了。“多态”的应用就是典型例子。

     

    参考地址: http://c.biancheng.net/view/267.html

    更多相关内容
  • JAVA中多态性是对象多种表现形式的体现。在面向对象中,最常见的多态发生在使用父类的引用来引用子类的对象。下面这篇文章主要给大家介绍一下,需要的朋友可以参考下
  • 多态 指同一个方法调用由于对象不同可能会产生不同的行为。 关于多态要注意以下几点: 多态是方法的多态,属性没有多态多态的存在有两个必要条件:继承、方法重写 代码如下 class Man: def eat(self): print('饿...
  • 深入理解c++多态实现原理

    万次阅读 2018-05-20 01:54:00
    多态是面向对象编程的核心思想之一,因此我们有必要深入探索一下它的实现原理。理解了原理才能更好的使用。前置条件 现有代码如下所示,非常简单的例子。通过基类的引用调用recv函数来触发多态。接下来的分析涉及...

    引言

           多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是面向对象编程的核心思想之一,因此我们有必要深入探索一下它的实现原理。理解了原理才能更好的使用。

    前置条件

            现有代码如下所示,非常简单的例子。通过基类的引用调用recv函数来触发多态。接下来的分析涉及汇编知识,如果还没熟悉汇编,可以看另外一篇文章<深入理解c++函数调用的参数传递与局部变量申请>

    #include <stdio.h>
    #include <string>
    
    #define trace(fmt, ...) printf("[trace] %s:%s:%d " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
    
    class IClient
    {
    	public:
    		IClient(){};
    		virtual ~IClient(){};
    		virtual ssize_t recv(char *buff, size_t len) = 0;
    };
    class CStreamClient: public IClient
    {
    	public:
    		CStreamClient(){};
    		~CStreamClient(){};
    		ssize_t recv(char *buff, size_t len)
    		{
    			trace("recv %d bytes in %p\n", len, buff);
    			return len;
    		}
    };
    int main(int argc, char **argv)
    {
    	CStreamClient streamclient;
    	IClient &client = streamclient;
    	client.recv(NULL, 0);
    	return 0;
    }

    分析

            我们都知道拥有虚函数的类都有属于自己的虚表存放在.text段中,实例化后对象拥有一个内建变量_vptr虚表指针,它指向了实际对象的虚表。那么这个_vptr在哪里初始化呢?当然是构造函数。如下图所示,CStreamClient与IClient都会在自己的构造函数中初始化_vptr为自己的虚表地址。不过父类构造先于子类构造函数执行,因此CStreamClient对象指向的是自己的虚表。


            接下来我们对main反汇编代码分析如下所示,client对象处于栈中。首先计算出client对象的地址。接下来解引用得到_vptr的值。值得注意的是_vptr并非指向虚表起始地址,而是+0X08。起始4字节猜测是保留,毕竟全是0x00;接下来4字节存放的是对象的类型信息,反汇编typeid()函数你就会发现它就是在读取这个地址指向的typeinfo。

            得到_vptr地址后+0x08的偏移得到虚表存放recv地址的地址,再解引用就得到实际绑定对象的recv函数地址了。接下来是各个参数入栈,再跳转执行。


            那么,这就结束了吗?眼尖的你肯定发现了,为什么会有两个的析构函数?函数声明还是一个模子出来的。那么我们对这两个析构反汇编分析对比一下有什么不同。有注意到什么不同吗?

            左侧的析构函数赋值eax为零,eax分为[16位ax|8位ah|8位al]。那么test指令将会置位ZF标志位,因此接下来的je指令将会跳转,也就是不进行free的操作。右侧的析构函数在调用左侧析构的基础上进行了释放内存。那么就是说它们分别是为栈上和堆上的对象准备的。


    展开全文
  • Java多态实现原理

    多人点赞 2021-06-03 16:06:38
    Java多态实现原理的大致过程:首先是Java编译器将Java源代码编译成class文件。在编译过程中,会根据静态类型将调用的符号引用写到class文件中。在执行时,JVM根据class文件找到调用方法的符号引用,然后在静态类型的...

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

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

    image

    程序运行时,需要某个类是,类载入系统会将相应的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 在方法区中的方法表可表示如下:

    可以看到,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 执行该调用指令的过程如下所示:

    (这里有个错误,上图为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
    大家看完有什么不懂的可以在下方留言讨论.
    谢谢你的观看。

    展开全文
  • java多态实现原理

    万次阅读 多人点赞 2016-07-22 16:46:24
    众所周知,多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。C++ 和 Java 作为当前最为流行的两种面向对象编程语言,其内部对于多态的支持到底是如何...

    众所周知,多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。C++ 和 Java 作为当前最为流行的两种面向对象编程语言,其内部对于多态的支持到底是如何实现的呢,本文对此做了全面的介绍。

    注意到在本文中,指针和引用会互换使用,它们仅是一个抽象概念,表示和另一个对象的连接关系,无须在意其具体的实现。

    Java 的实现方式

    Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用和接口引用调用的实现则有所不同。总体而言,当某个方法被调用时,JVM 首先要查找相应的常量池,得到方法的符号引用,并查找调用类的方法表以确定该方法的直接引用,最后才真正调用该方法。以下分别对该过程中涉及到的相关部分做详细介绍。

    JVM 的结构

    典型的 Java 虚拟机的运行时结构如下图所示

    图 1.JVM 运行时结构
    图 1.JVM 运行时结构

    此结构中,我们只探讨和本文密切相关的方法区 (method area)。当程序运行需要某个类的定义时,载入子系统 (class loader subsystem) 装入所需的 class 文件,并在内部建立该类的类型信息,这个类型信息就存贮在方法区。类型信息一般包括该类的方法代码、类变量、成员变量的定义等等。可以说,类型信息就是类的 Java 文件在运行时的内部结构,包含了改类的所有在 Java 文件中定义的信息。

    注意到,该类型信息和 class 对象是不同的。class 对象是 JVM 在载入某个类后于堆 (heap) 中创建的代表该类的对象,可以通过该 class 对象访问到该类型信息。比如最典型的应用,在 Java 反射中应用 class 对象访问到该类支持的所有方法,定义的成员变量等等。可以想象,JVM 在类型信息和 class 对象中维护着它们彼此的引用以便互相访问。两者的关系可以类比于进程对象与真正的进程之间的关系。

    Java 的方法调用方式

    Java 的方法调用有两类,动态方法调用与静态方法调用。静态方法调用是指对于类的静态方法的调用方式,是静态绑定的;而动态方法调用需要有方法调用所作用的对象,是动态绑定的。类调用 (invokestatic) 是在编译时刻就已经确定好具体调用方法的情况,而实例调用 (invokevirtual) 则是在调用的时候才确定具体的调用方法,这就是动态绑定,也是多态要解决的核心问题。

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

    常量池(constant pool)

    常量池中保存的是一个 Java 类引用的一些常量信息,包含一些字符串常量及对于类的符号引用信息等。Java 代码编译生成的类文件中的常量池是静态常量池,当类被载入到虚拟机内部的时候,在内存中产生类的常量池叫运行时常量池。

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

    CONSTANT_Utf8_info

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

    CONSTANT_Class_info

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

    CONSTANT_NameAndType_info

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

    CONSTANT_InterfaceMethodref_info

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

    CONSTANT_Methodref_info

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

    图 2. 常量池各表的关系
    图 2. 常量池各表的关系

    可以看到,给定任意一个方法的索引,在常量池中找到对应的条目后,可以得到该方法的类索引(class_index)和名字类型索引 (name_and_type_index), 进而得到该方法所属的类型信息和名称及描述符信息(参数,返回值等)。注意到所有的常量字符串都是存储在 CONSTANT_Utf8_info 中供其他表索引的。

    方法表与方法调用

    方法表是动态调用的核心,也是 Java 实现动态调用的主要方式。它被存储于方法区中的类型信息,包含有该类型所定义的所有方法及指向这些方法代码的指针,注意这些具体的方法代码可能是被覆写的方法,也可能是继承自基类的方法。

    如有类定义 Person, Girl, Boy,

    清单 1
     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 在方法区中的方法表可表示如下:

    图 3.Boy 和 Girl 的方法表
    图 3.Boy 和 Girl 的方法表

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

    Person 或 Object 的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。

    如调用如下:

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

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

    Invokevirtual #12

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

    图 4. 解析调用过程
    图 4. 解析调用过程

    JVM 首先查看 Party 的常量池索引为 12 的条目(应为 CONSTANT_Methodref_info 类型,可视为方法调用的符号引用),进一步查看常量池(CONSTANT_Class_info,CONSTANT_NameAndType_info ,CONSTANT_Utf8_info)可得出要调用的方法是 Person 的 speak 方法(注意引用 girl 是其基类 Person 类型),查看 Person 的方法表,得出 speak 方法在该方法表中的偏移量 15(offset),这就是该方法调用的直接引用。

    当解析出方法调用的直接引用后(方法表偏移量 15),JVM 执行真正的方法调用:根据实例方法调用的参数 this 得到具体的对象(即 girl 所指向的位于堆中的对象),据此得到该对象对应的方法表 (Girl 的方法表 ),进而调用方法表中的某个偏移量所指向的方法(Girl 的 speak() 方法的实现)。

    接口调用

    因为 Java 类是可以同时实现多个接口的,而当用接口引用调用某个方法的时候,情况就有所不同了。Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同样的方法在基类和派生类的方法表的位置就可能不一样了。

    清单 3
    interface IDance{ 
       void dance(); 
     } 
    
     class Person { 
     public String toString(){ 
       return "I'm a person."; 
    	 } 
     public void eat(){} 
     public void speak(){} 
    	
     } 
    
     class Dancer extends Person 
     implements IDance { 
     public String toString(){ 
       return "I'm a dancer."; 
    	 } 
     public void dance(){} 
     } 
    
     class Snake implements IDance{ 
     public String toString(){ 
       return "A snake."; 
    	 } 
     public void dance(){ 
     //snake dance 
    	 } 
     }
    图 5.Dancer 的方法表(查看大图
    图 5.Dancer 的方法表

    可以看到,由于接口的介入,继承自于接口 IDance 的方法 dance()在类 Dancer 和 Snake 的方法表中的位置已经不一样了,显然我们无法通过给出方法表的偏移量来正确调用 Dancer 和 Snake 的这个方法。这也是 Java 中调用接口方法有其专有的调用指令(invokeinterface)的原因。

    Java 对于接口方法的调用是采用搜索方法表的方式,对如下的方法调用

    invokeinterface #13

    JVM 首先查看常量池,确定方法调用的符号引用(名称、返回值等等),然后利用 this 指向的实例得到该实例的方法表,进而搜索方法表来找到合适的方法地址。

    因为每次接口调用都要搜索方法表,所以从效率上来说,接口方法的调用总是慢于类方法的调用的。

    展开全文
  • Java 多态实现原理

    2021-02-27 19:49:57
    1、多态的机制1.1 本质上多态分两种1、编译时多态(又称静态多态)2、运行时多态(又称动态多态)重载(overload 发生在一个类中,方法名必须相同,不同参数)就是编译时多态的一个例子,编译时多态在编译时就已经确定,...
  • 文章目录前言有虚函数的类对象模型派生类继承有虚函数的基类的对象模型无虚函数派生类继承有虚函数基类的对象模型有虚函数派生类继承有虚函数的基类对象模型多态原理剖析汇编代码分析多态的过程有关多态的常见几个...
  • Java基础(多态实现原理)

    万次阅读 多人点赞 2022-04-22 12:50:32
    Java基础(多态实现原理)
  • 多态实现原理

    2021-05-14 09:37:07
    多态实现原理 多态的概念 多态的构成条件 虚函数 虚函数的重写(覆盖) 多态的原理 虚函数表指针及虚函数表 派生类的虚函数表 多态的概念 顾名思义,多态即多种形态。 定义:不同继承关系的类对象,调用同一函数,...
  • Java多态实现原理

    2021-02-12 12:54:07
    多态的使用大家应该都比较了解,但是多态实现原理就有点抽象了,查了很多很多资料,连续几天断断续续的看,有时候看着看着就走神了。毕竟太抽象,哈哈~不过依然硬着头皮看下来了(也不知道看了多少遍),并且将很多...
  • Java中多态实现原理

    2022-01-05 10:50:13
    1、什么是多态 多态是一个类的多种形式;...3、多态实现原理 通过invoke指令调用方法,基于class 4、什么是方法表 方法表存放方法地址,通过invoke指令调用对应方法,如果没有重写方法调用父类里的方法 ...
  • 1 实现多态的三步骤 1、 有继承关系的类 2、父类有虚函数,子类重写父类的虚函数 3、子类的指针或应用赋值给父类 2 虚函数注意点 派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。...
  • C++多态实现原理

    2020-10-24 16:20:02
    动态多态就是通过继承重写基类的虚函数实现多态,因为实在运行时决议确定,所以称为动态多态。运行时在虚函数表中寻找调用函数的地址。 在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据...
  • Java 中多态实现原理

    千次阅读 2021-08-26 16:59:08
    多态是面向对象编程里面的概念,一个接口的多种不同的实现方式,即为多态。 这里的接口不应理解得太死板,比如在 Java 里面,继承一个类和实现一个接口本质上都是一种继承行为,因此都可以理解为多态的体现。 从...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 69,043
精华内容 27,617
关键字:

多态的实现原理

友情链接: JQuery-zTree-v2.6.rar