多态在内存中的实现--虚函数表

引入

虚函数本是c++中的概念,但在java中应用非常广泛,因为虚函数是为了实现多态而生,需要为函数添加vritual声明。但在java中,所有类都可以被继承,所有方法都可以在子类中重写,多态的实现完全不需要担心,结论好像已经显而易见了,但为了说明java多态在虚拟机中的实现原理,还是要从c++的虚函数写起。

虚函数的定义

在某[基类]中声明为 virtual 并在一个或多个[派生类]中被重新定义的[成员函数]。
用法格式为:virtual 函数返回类型函数名(参数表) {[函数体]};实现[多态性],通过指向派生类的基类[指针]或引用,访问派生类中同名覆盖成员函数。
举例说明:

#include<iostream>
using namespace std;
class A
{
    public:
        void virtual FUN()
        {
            cout<<"FUN in A is called"<<endl;
        }
};
 
class B : public A
{
    public:
        void FUN()
        {
            cout<<"FUN in B is called"<<endl;
        }
};
 
int main()
{
    A a;
    B b;
    A *p;
    p = &a;
    p->FUN();
    p = &b;
    b.FUN();
    return 0;
}

结果:

FUN in A is called
FUN in B is called

如果去掉virtual,结果会是两个FUN in A is called。
通过这个例子,可以看到虚函数的作用------实现多态。指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。

java实现的多态

而在java中就没有相关声明了,直接extends父类,重写一个同名方法即可完成方法的覆盖。


class A{
    public void FUN(){
        System.out.println("FUN in A is called");
    }
}
class B extends A{
    public void FUN(){
        System.out.println("FUN in B is called");
    }
}
public class VirtualTest {
 
    public static void main(String args[])  {
        A a = new A();
        B b = new B();
        a.FUN();
        b.FUN();
    }
}

运行结果:

FUN in A is called
FUN in B is called

在上面的代码中,我们同样定义了一个基类指针(在java中应该叫引用)去指向不同的对象,可以发现同样可以实现多态。也就是说,java的普通成员方法(没有被static、native等关键字修饰)就是虚函数,原因很简单,它本身就实现虚函数实现的功能------多态。

字节码验证

我们首先通过javac命令编译java文件,在使用javap -verbose VirtualTest查看字节码文件,如下图


image.png

得到字节码文件如下图


image.png

上面字节码中,有许多指令,如aload、invokespecial、invokevirtual(注:aload指令是用来将数据加载到栈,invokespecial用来调用类的构造函数,invokevirtual用来调用普通函数)。可以发现,对于A.FUN的调用,java虚拟机所采用的调用方式就是invokevirtual。这些字节码的调用指令,是由《java虚拟机规范》规定的,在制定规范之初,大牛们就直接用invokevirtual来命名调用普通函数的指令。从字节码指令的命名上也可以看出,java中的普通成员函数就是虚函数。

结论:
C++--------------------Java
虚函数 -------- 普通方法
纯虚函数 -------- 抽象方法
抽象类 -------- 抽象类
虚基类 -------- 接口
java类中普通成员方法就是虚函数。
ps:静态方法不能被重写,所以也不存在静态方法的多态。

多态在内存中的实现

让我们从一个简单的类开始说明,shape类存放两个字段,x,y,他们在内存中是这样存放的:

image.png

如果加入子类circle,继承shape,子类增加一个字段,那么cricle对象在内存中是这样的:


image.png

上面都没什么问题,但如果父类一个方法,在内存中怎么放置?每个子类对象都做一次拷贝?那太浪费了。我们可以把这个方法在内存中生成一份,然后在每个对象上增加一个指针,指向这个方法就行了。


image.png

但是问题又来了,如果父类又加了一个方法,我们又要多上n个指针。


image.png

如果方法很多,对象也很多,那么实际指针数将是m*n。还是浪费。那么有什么解决办法呢?很明显,我们需要一个中间层,这个中间层把所有函数的指针都记下来。这个中间层就是虚函数表。

image.png

每个类,只要维持一个虚函数表就可以了。
每个对象,只要记录一个虚函数表的地址就可以了。

为什么叫做虚函数表呢?这个概念可能是从C++中来的,在C++中有个关键字virtual ,修饰一个函数的时候,这个函数就会变为虚函数,在调用时就具备了多态的行为。(注:在Java中,一个类的函数默认都是虚函数)
那多态到底是怎么实现的呢?
非常简单,只要把虚函数表给设置好就行了。假设子类Circle 也定义了一个move 函数,把父类Shape的move函数覆盖了,在内存将会是这个样子:

image.png

当你调用circle.draw()的时候,在虚函数表中找到的还是Shape类的draw()方法。
但是当调用circle.move()的时候,就会从Circle类的虚函数表中找到Circle.move(),而不是Shape.move(),多态发生了!
仔细看看上面这张图,在内存中,三个方法和两个对象是分开的,这里没有Class的概念,多态是通过虚函数表实现的。如果我们写程序的时候,写下这样的函数Shape_draw(), Shape_move(), Circle_move(),再写下Shape和Circle这样的数据结构,然后把他们用虚函数表连接到一起。也就实现了面向对象了。
在内存中,“面向对象”已经褪去漂亮的包装,退化成“面向过程”, 退化成那个最基本的公式:程序 = 数据结构 + 算法。
当然,在绝大部分情况下,程序员不需要手工地去实现这个虚函数表,这件事情应该交给机器去做。
对于C++,编译器可以在编译期间生成虚函数表。对于Java,编译出的字节码中是没有的,只有invokevirtual这样的指令,虚函数表是在类装入虚拟机的时候创建的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351