1. 我理解的广义的 override 是指抛开各种访问权限,子类重定义(redefine)父类的函数(即函数签名相同)。

2. C++中的三个所谓的原则:never redefine base class‘ non-virtual function;重写虚函数;如果要 redefine 父类的 static 函数的话,就同样也写成static函数。

虽然C++允许很多事情,但并不意味着我们就要这样做,相反,很多事情是需要极力避免的。

除了virtual funtion 的 override 的函数返回值需要相同或者协变(即 incompatible),其余情况均对函数返回值无要求。

函数签名相同即意味着隐藏父类函数,当然需要注意C++中的name hiding问题。

3. Java中的大部分方法都是virtual method,除了构造方法,static method,private method,final method。

Java中秉承一个原则:不能继承的就不能重写。但是C++并不是,所以C++的virtual function可以是private访问权限的。

与Java中override父类方法必须放宽访问权限而C++无此要求一样,我个人认为这些都体现了Java的安全性。

4. 在Java中有几种很特殊的情况:

① redefine父类的 private 方法时,因为 private 方法是所谓的隐式 final 的,是不会参与多态的,也对应着上面提到的不能继承的就不能重写。

所以相当于在子类中 redefine 了一个相同签名的方法,返回值当然无要求,因为本来就没有继承父类的方法所以也没有隐藏一说,可以算作我上面所说的广义的override,哈哈。

② 子类不在父类所在的包中,父类中包权限访问的方法子类无法继承,也就无法重写。

但是包访问权限方法却毫无疑问是virtual method,当子类确实 redefine 了父类的包访问权限方法时,这时候用指向子类对象的父类引用来调用这个方法时,实际上也是动态

调用,但是调用到的还是父类的方法,关于这个的实现原理,我还专门上知乎咨询了R大,见:知乎

当然返回值同样无要求,因为本来就没有继承父类的包权限方法所以同样也没有隐藏一说,也可以算作我上面所说的广义的override,哈哈。

注意:在实际的编码过程中,上面的这两种情况是应该尽量杜绝的,因为容易给人造成误解,写出来的代码也不 effective 。

③ redefine 父类的 static 方法时,JLS的官方说法是 hide,当然这也可以算作我上面所说的 广义的 override。

这时候Java的要求 是返回值 也不能不 incompatible,实际上是不需要的,但是规定就这么定了,那就这样咯。

5. 关于多态的实现,大家都知道就是靠虚函数表,网上这篇流传甚广的文章:C++虚函数表解析

Java的实现原理与C++类似,JVM中有一块内存区域叫做方法区,JVM也会为每一个类建立一个 method table 来存放方法的索引地址。

两者有很多相同之处:
     1、它们的作用是相同的,同样用来辅助实现方法的动态绑定。
     2、同样是类级别的数据结构,一个类的所有对象共享一个方法表。
     3、都是通过偏移量在该数据结构中查找某一个方法。
     4、同样保证所有派生类中继承于基类的方法在方法表中的偏移量跟该方法在基类方法表中的偏移量保持一致。
     5、方法表中都只能存放多态方法(即virtual function)。

但是但是归根结底,C++是一门编译型的语言,而Java更加偏向于解析型的,因此上述数据结构的生成和维护是有所不同的,表现在:
     1、C++中 VTable 和 vptr 是在编译阶段由编译器自动生成的,也就是说,在C++程序载入内存以前,在.obj(.o)文件中已经有这些结构的信息;Java中的方法表是由JVM生成的,因此,使用 javac 命令编译后生成的.class文件中并没有方法表的信息。只有等JVM把.class文件载入到内存中时,才会为该.class文件动态生成一个与之关联的方法表,放置在JVM的方法区中。
    2、C++中某个函数在 VTable 的索引号是在编译阶段已经明确知道的,并不需要在运行过程中动态获知;Java中的方法初始时都只是一个符号,并不是一个明确的地址,只有等到该方法被第一次调用时,才会被解析成一个方法表中的偏移量,也就是说,只有在这个时候,实例方法才明确知道自己在方法表中的偏移量了,在这之前必须经历一个解析的过程。

流程:调用方法时,虚拟机通过对象引用得到方法区中类型信息的方法表的指针入口,查询类的方法表 ,根据实例方法的符号引用解析出该方法在方法表的偏移量,子类对象声明为父类类型时,形式上调用的是父类的方法,此时虚拟机会从实际的方法表中找到方法地址,从而定位到实际类的方法。 
注:所有引用为父类,但方法区的类型信息中存放的是子类的信息,所以调用的是子类的方法表。

  参考:多态的实现机制(Java篇)Java多态的实现原理

6. 关于父类是抽象类,多态的实现,可以简单的理解为:在虚函数表中,纯虚函数(抽象方法)同样有自己对应的位置(即索引),可以在调用时利用偏移量来动态调用,

但是不同的是,这些指针的值是NULL,因为他们在父类中并没有给出实现。

7. C++和Java在多态实现中,最大的不同就在于C++的多重继承与Java的接口。

① 先说Java,Java调用接口的方法时JVM是有一个单独的指令的,就是invokeinterface,根据知乎R大所说,实际上每个接口的方法也是相当于有一个方法表,

就是ITable,只不过可以看做这里面的函数指针都指向NULL。但是因为Java的类可以实现多个接口,所以一个接口的方法在子类方法表中的偏移量总是会不一样的,

所以我们没有办法像调用普通类的virtual function一样通过偏移量来实现多态,所以需要另一个新的指令来调用接口方法,就是invokeinterface。

而通过接口引用来调用子类重写的方法时,基本的方法是采用的遍历搜索的思想,遍历找寻子类方法表,直到找到对应的方法实现。

所以一般来讲调用接口方法都会比较慢,但是各大VM实现肯定都会有自己大量的优化方案,只不过R大所说的我还一点都看不懂。

② 而C++的多重继承采取的方式就很巧妙了,借用上面陈皓文章里的一幅图,一目了然:

每个父类都有自己对应的虚表,相当于建立了三张表,这样就可以继续通过父类指针,然后确定偏移量,然后高效地调用子类的虚函数表。

设计比较精巧。当然C++的多重继承是一个很复杂的问题,要严格慎用,我理解的还非常肤浅。

参考IBM社区的这篇文章:多态在C++和Java编程语言中的实现比较

8. 关于Java8接口中的 default 方法

众所周知,Java8中接口可以定义default方法,而 default 方法毫无疑问是 virtual 的,实现类或继承的接口可以覆写。

关于接口的 default 方法,也有三个原则:

类优先于接口;继承的深度会起到作用(顺便一提在C++中只有使用了虚继承才会体现出来继承的深度,默认情况下的多继承并不会体现出来);继承或实现多个接口

时有相同方法签名的方法冲突时必须加以重写覆盖(顺便C++并不强制要求)。

关于 有 默认方法的情况下的 实现了接口的 类的方法表的结构,我猜测是这样的:

最前面是直接父类的方法表,然后接着是实现的第一个接口的方法表,先是默认方法,然后是 抽象方法的实现,接着按顺序是实现的其他的接口的方法表。

所以这时候可以猜测 invokeinterface的基本方法还是 搜索方法表,因为同样不能确定偏移量。

而相同签名的 默认方法 必须覆盖掉,这就是语言设计者 的 taste 不同了,感觉 C++ 多重继承时的不强制要求覆盖,但是调用的时候会有二义性,需要加上父类作用域

访问符也比较人性化,当然Java的强制覆盖一定程度上也体现了 C++--的特征。哈哈

9. 关于下面这种情况,个人感觉是无解的情形:

 interface A{
default int fun(){
return 0;
}
} interface B{
default int[] fun(){
int[] a = {0};
return a;
}
}
public class Test implements A, B{
public int fun()
{
System.out.println("World");
return 0;
}
public static void main(String[] agrs) {
Test obj = new MyClass();
obj.fun();
}
}

上述代码是有错的,因为两个接口中相同签名的方法的 返回值 不同,所以没有方法对它们进行重写,Java 和 C++ 中都是如此。

自己编码过程中要尽量避免这种情况。

10. 一个可能没啥用的 tips,Java:packA.Aclass.fun;C++:Anasp::Aclass::fun,当然Java8中配合Lambda使用的方法引用也引入了::这个符号,

总的来说,就是符号的问题,不值一提()。

11. 以上就是全部的自己最近一段时间的关于多态的理解,很多地方都是自己的类比与猜测,限于知识水平还没有较好的办法进行查证。

随着学习的深入以及知识的增多,自己难免会发现以上的很多文字可能会存在疏漏甚至错误,也很正常,改之即可。

最后,别忘了学习编程的奥义:Keep Coding!Keep Coding!!Keep Coding!!!

2017.2.25更新:

Java的接口继承本质上还是类似于C++的多继承,尤其是在接口中引入默认方法以后就更像了。

只不过Java是单继承体系,所以在接口引用调用虚方法时只能是遍历的方法,而不是像C++多继承那样的多层虚函数表直接采用偏移量。

有两种情况自己纠结了很久要特别注意一下,就是所谓的两者(多者)择一(指向的方法相同选哪个作为偏移量都无妨),两者(多者)中必须选某一个,(指由于

继承链的深度导致不同接口(父类C++)中的相同的虚方法隐藏与被隐藏的关系、或者JAVA中的类优先于接口)。

还有就是所谓的方法引用(虚函数指针)在一个类中是不能为NULL的,这一点在实现接口或者继承抽象类的时候要注意。

以上所说的都是自己纠结了不短时间的点,这些小tip在自己心中也都对应有代码片段,自己以后看到了也能马上想起来。

最后想说的还是,写代码写代码写代码写代码写代码,Keep Coding!!!

附上知乎R大对自己理解虚函数表有用的一个回答:知乎

多态(Polymorphism)的实现机制的更多相关文章

  1. 7.6 GRASP原则六: 多态 Polymorphism

    GRASP原则六: 多态 Polymorphism  How to handle alternative behaviors based on type 如何处理依据类型不同而有 不同行为的一类需求 ...

  2. 《编程导论(Java)·2.1.2 啊,我看到了多态》-什么是多态(polymorphism)

    1.不明觉厉 很多人学习多态时,会认为. 之所以不明觉厉,由于多态的定义:事物存在的多种表现形态:而后,有人将重载(overload).改写(override).多态变量和泛型归结于同一个术语&quo ...

  3. java面向对象之 多态 Polymorphism

    多态(Polymorphism):用我们通俗易懂的话来说就是子类就是父类(猫是动物,学生也是人),因此多态的意思就是:父类型的引用可以指向子类的对象. 1.多态的含义:一种类型,呈现出多种状态 主要讨 ...

  4. C++ 多态Polymorphism 介绍+动态绑定、静态绑定

    什么是多态? 多态(polymorphism)一词最初来源于希腊语polumorphos,含义是一种物质的多种形态. 在专业术语中,多态是一种运行时绑定机制(run-time binding) ,通过 ...

  5. 多态polymorphism,向上转型和动态方法调度有什么用?

    多态有什么用?马 克  -   t   o - w   i  n:https://blog.csdn.net/qq_44639795/article/details/103117332我给大家想了两个 ...

  6. Java笔记(十五)……面向对象IV多态(polymorphism)

    概述 定义:某一类事物的多种存在形态. 例:动物中猫,狗. 猫这个对象对应的类型是猫类型 猫 x = new 猫(); 同时猫也是动物中的一种,也可以把猫称为动物. 动物 y = new 猫(); 动 ...

  7. [18/12/03] 多态(polymorphism)和对象的转型(casting)

    一.多态 多态指的是同一个方法调用,由于对象不同可能会有不同的行为.现实生活中,同一个方法,具体实现会完全不同. 比如:同样是调用人的“休息”方法,张三是睡觉,李四是旅游,同样是调用人“吃饭”的方法, ...

  8. Java学习之 多态 Polymorphism

    转自:http://www.cnblogs.com/mengdd/archive/2012/12/25/2832288.html 多态的概念 多态==晚绑定. 不要把函数重载理解为多态. 因为多态是一 ...

  9. C++ 多态、虚函数机制以及虚函数表

    1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a  = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2. ...

随机推荐

  1. python配置主机名

    .准备hosts模板 mkdir -p /k8s/profile cat >/k8s/profile/hosts<<EOF 192.168.0.91 test1 192.168.0. ...

  2. shader飞线改进版

    项目github地址:https://github.com/ecojust/flyline 前面写过一个飞线(基于THREE.Line进行的颜色变化),只是简单地将可视区片元颜色的alpha通道值设为 ...

  3. message-digest algorithm 5

    using System; using System.Collections.Generic; using System.Text; using System.Security.Cryptograph ...

  4. 嵌入式【杂记--手机芯片与pc】

    手机.身边的移动设备大多数是嵌入式计算机,pc也是计算机,只是功耗上很大. 手机所采用的大多数芯片是英国ARM公司的架构coretom A系列 core, Intel公司采用自己的架构设计的芯片适用于 ...

  5. sqlalchemy链接数据库

    from sqlalchemy import create_engine HOSTNAME = '127.0.0.1' PORT = 3306 DATABASE = 'first_sqlalchemy ...

  6. 数据库 ----jdbc连接池的弊端

    jdbc连接池的弊端 1.数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响 数据库性能.设想:使用数据库连接池管理数据库连接.2.将sql语句硬编码到 ...

  7. 【案例分享】SpreadJS金融行业应用实践,开发基于Web Excel的指标补录平台

    SpreadJS作为一款基于 HTML5 的纯前端电子表格控件,以“高速低耗.高度类似Excel.可无限扩展”为产品特色,提供移动跨平台和浏览器支持,可同时满足 .NET.Java.App 等应用程序 ...

  8. 多边形面积(Area_Of_Polygons)

    原理: 任意多边形的面积可由任意一点与多边形上依次两点连线构成的三角形矢量面积求和得出. 分析: 由于给出的点是相对于我们的坐标原点的坐标,每个点实际上我们可以当作一个顶点相对于原点的向量,如下图所示 ...

  9. Souvenirs CodeForces - 765F (好题)

    大意: 给定序列$a$, $m$个询问$[l,r]$, 回答$[l,r]$内最接近的两个数的差. 考虑离线, 枚举右端点, 每个点维护左端点的贡献, 对于新添一个点$a_r$, 只考虑左侧点比$a_r ...

  10. leetcode hard

    # Title Solution Acceptance Difficulty Frequency     4 Median of Two Sorted Arrays       27.2% Hard ...