很多人总是喜欢,或者说错误地将JAVA中的多态理解得很复杂,最常见的错误说法就是所谓“方法的多态”,他们会给出类似下面的例子来佐证“多态是指方法的多态”:

//Enginner和Mechanic是Employee的子类,构造函数参数均为月薪salary
Employee a=new Enginner(100);
Employee b=new Mechanic(100); //getAnnualSalary是Employee类的方法,用于计算并返回年薪
System.out.println(a.getAnnualSalary());//输出1500,Enginner年薪为15倍月薪
System.out.println(b.getAnnualSalary());//输出1300,Mechanic年薪为13倍月薪

  从结果上看,a、b都是Employee类对象变量,然而对a调用getAnnualSalary()返回的是15*salary,对b调用getAnnualSalary()却返回了13*salary,好像的确是所谓“方法的多态”,毕竟对同一类对象变量调用同一个方法,内部实现方式却出现不同了嘛。基于这样的想法,甚至有一些人将多态扩展到了更广泛、更复杂的情况,比如下面这种,连泛型都算进了多态中:

  

  那么,多态真的是有那么多种情况吗?真的是只要方法名相同,而参数或者内部实现方式不同,就要看成是多态吗?不不不,这种说法纯属扯淡,JAVA中的多态有且只有一种情况:对象变量是多态的。这个理解至关重要,可以说对于多态的概念,要记住的就是这个点。但是,为什么在上面的例子中,对a和b调用同一个方法,会有不同的效果呢?注意,这是方法调用的知识范畴,只是恰好和多态相关罢了。下面我们就来理清一下多态与方法调用的知识。

  JAVA中的多态是由继承机制带来的,正是因为有继承机制,所以才存在多态。简单来说,多态的起因就是JAVA中允许一个父类对象变量引用一个子类对象(至于为什么我们之后会说):

//Son是Father的子类
Father variable=new Son(); //variable是一个Father类对象变量,但它实际引用的对象却是Son类对象

  由于父类对象变量可以引用子类对象,所以当我们看到一个A类对象变量时,我们不能一口咬定它所引用的对象就是A类对象,它也有可能引用B类对象、C类对象……只要它引用的对象是A类的子类对象就行。这就是多态:对象变量实际引用的对象的类型不一定是对象变量声明的类型。

  但是单纯的多态并没卵用,我令Employee类对象变量a引用了一个Enginner对象,然后呢,即便我在Enginner中重写了getAnnualSalary以返回15薪,在对a调用getAnnualSalary时依然返回12薪吗?(假设Employee类中getAnnualSalary返回12*salary)那有什么意义?

  所以实际上,多态的存在,必须要有方法调用时的动态绑定支持才有意义。所谓方法调用的动态绑定,就是:虚拟机会调用与变量所引用的实际类型最匹配的那个方法。

  举例来说,Employee类的getAnnualSalary返回12*salary,Enginner类重写了该方法以返回15*salary,那么当出现下述情况时:

Employee a=new Enginner(100);
int annualSalary=a.getAnnualSalary();

  虚拟机会先判断变量a所引用的对象实际上是什么类型(此例实际类型为Enginner),然后查看其实际类型是否重写了该方法(此例Enginner重写了Employee中的getAnnualSalary方法),如果是则调用其实际类型中的该方法(此例也即调用Enginner类中返回15*salary的getAnnualSalary),否则调用a声明的类型(即Employee)中的该方法。

  通过多态+动态绑定,我们就可以快速地实现一些效果。比如说写一个抽象类List,声明一个get方法以获取列表中指定元素,声明一个set方法以设置列表中指定元素,然后实现一个非抽象子类LinkedList,内部采用链表结构存储列表,再实现一个ArrayList,内部采用数组结构存储列表。这样一来,我们就可以利用多态+动态绑定这样写代码:

List a=new ArrayList();
oldValue=a.get(i);
a.set(i,newValue);

  如果我们想要使用一个可以良好支持随机访问的列表,我们就可以像上面这样写,即令a引用一个ArrayList对象,如果哪一天我们希望此处改用使用良好支持动态增减的列表了,只需要将

List a=new ArrayList();

  改为:

List a=new LinkedList();

  即可,而其余代码不需要改动。通过方法的动态绑定,对get和set的调用都将自动成为对LinkedList类中的方法调用。这样一来,改变列表的实际存储结构就成了一个很简单的事情。

  此外,多态+动态绑定还可以在“只关注通用方法”时起到简化代码的效果。什么意思呢?举例来说就是Enginner和Mechanic有各自不同的,在Employee类基础上新增的方法。但是我们在统计员工薪水时,并不想关注它们各自独有的东西,只想关注同样作为Employee都会有的年薪。那我就可以将各个Enginner、Mechanic都放进一个Employee数组中,然后遍历该数组,对每个元素调用getAnnualSalary并输出,而不用为Enginner创个数组遍历一遍,再对Mechanic创个数组遍历一遍。

  当然,多态+动态绑定还有许多其他用途,尤其是在JAVA的各集合类应用上,此处不予细谈。

  如果说动态绑定是解决了多态的方法调用问题,那么静态绑定就是为了快速实现(方法)重载机制。所谓重载机制就是指在JAVA中,允许一个方法的名字与已存在的另一个方法相同,只要这两个方法的参数个数或类型不同即可。这种多个方法名字相同、参数不同的情况,就是方法重载。此处所说的“方法”也可以是构造器,因此这种机制叫做:重载。

  要想实现重载,就得在调用方法时,根据调用时所给的参数决定到底调用哪个方法。但是到底该什么时候确定这件事呢?在JAVA中,这个确认步骤在编译器将源代码翻译为字节码时确定,也即由编译器javac根据方法调用时所给的参数个数、类型来确定实际该调用哪个方法,从而实现重载。因为是在编译时确定的,所以这个绑定过程就是静态绑定。

  但是需要注意的是,静态绑定并不算真正的“绑定”,它其实是一个筛选。什么意思呢?举例来说,假设Employee类的getAnnualSalary还有一个带参数的版本:getAnnualSalary(double bonusRate),即给定一个“奖金比例”来计算年薪,那么当对一个Employee类对象变量a调用getAnnualSalary()时,编译器会先进行静态绑定,即筛选,从而确定此处的方法调用不可能是带参数的版本,但有可能是Employee类的该方法,也有可能是Enginner或Mechanic类的该方法,经过静态绑定后,剩下了三种可能,再由虚拟机在运行时通过动态绑定确定真正调用的方法。

  其实重载也可以做成让虚拟机来做的事情,但是通过编译器的静态绑定筛选掉一部分方法,就可以令虚拟机在确定实际调用方法时减少一些工作量,只关注于动态绑定的可能方法上。所以说静态绑定是为了快速实现重载。

  有关多态、方法调用的相关知识当然还有许多细节,比如一个方法x(int)和重载的方法x(double),在调用x(3)时既可以是调用x(int),也可以是调用x(double),到底选哪个?为什么重载不允许仅仅返回类型不同?不过这些细节问题并不是本文想要讨论的东西,本文要说的基本上就是上面那些提纲挈领的内容。

  总的来说,在学习JAVA多态时最重要的点就是要明白多态就是指对象变量的多态,不要去把多态这个概念复杂化。至于所谓“方法的多态”,其实就是方法调用的静态绑定(筛选)和动态绑定。

JAVA:一篇文章理清多态的更多相关文章

  1. 一篇文章理清WebGL绘制流程

    转自:https://www.jianshu.com/p/e3d8a244f3d9 目录 初始化WebGL环境 顶点着色器(Vertex Shader)与片元着色器(Fragment Shader) ...

  2. N个任务掌握java系列之统计一篇文章中单词出现的次数

    问题:统计一篇文章中单词出现的次数 思路: (1)将文章(一个字符串存储)按空格进行拆分(split)后,存储到一个字符串(单词)数组中. (2)定义一个Map,key是字符串类型,保存单词:valu ...

  3. 一篇文章读懂Java类加载器

    Java类加载器算是一个老生常谈的问题,大多Java工程师也都对其中的知识点倒背如流,最近在看源码的时候发现有一些细节的地方理解还是比较模糊,正好写一篇文章梳理一下. 关于Java类加载器的知识,网上 ...

  4. “全栈2019”Java第五十七章:多态与构造方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  5. “全栈2019”Java第五十六章:多态与字段详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  6. “全栈2019”Java第五十四章:多态详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  7. 面试题-关于Java线程池一篇文章就够了

    在Java面试中,线程池相关知识,虽不能说是必问提,但出现的频次也是非常高的.同时又鉴于公众号"程序新视界"的读者后台留言让写一篇关于Java线程池的文章,于是就有本篇内容,本篇将 ...

  8. 一篇文章弄懂 Java 反射的使用

    说到Java反射,必须先把 Java 的字节码搞明白了,也就是 Class , 大 Class 在之前的文章中,我们知道了Java的大Class就是类的字节码,就是一个普通的类,里面保存的是类的信息, ...

  9. 一篇文章通俗易懂的让你彻底理解 Java 注解

    很多Java程序员,对Java的注解一知半解,更有甚者,有的人可能连注解是什么都不知道 本文我们用最简单的 demo , 最通俗最短的语言,带你了解注解到底是什么? 先来简单回顾一下基础,我们知道,J ...

随机推荐

  1. ACdream 1031 Cut

    题意:给定一棵树,删除一些边,让整棵树被分成多个节点数为偶数的联通块,且联通块尽量多. 思路:如果出现连通且节点数为偶数的立即删除这个点与它父节点之间的边,尽量删除即可,因为题目说了保证n为偶数,删了 ...

  2. uva225 回溯剪枝

    这道题要剪枝,不然会超时,还有就是每次参加过的城市下次不能再参观,不然会WA. 对于障碍物的坐标我用了两种方法,第一种就是直接用STL里面的set,对于判断是否访问过直接用的count,用时960ms ...

  3. POJ - 3279 枚举 [kuangbin带你飞]专题一

    这题很经典啊,以前也遇到过类似的题--计蒜客 硬币翻转. 不过这题不仅要求翻转次数最少,且翻转方案的字典序也要最小. 解法:二进制枚举第一行的翻转方案,然后处理第二行,如果第二行的k列的上一列是黑色, ...

  4. MongoDB,子查询

    //1.从sub(订单明细)对订单号分组,查询最多子订单的单号一条数据,重命名orderNo字段为num//2.根据这个sub.num(从结果集里获取第一条结果),查询main(主档表) db.mai ...

  5. [Note] Apache Flink 的数据流编程模型

    Apache Flink 的数据流编程模型 抽象层次 Flink 为开发流式应用和批式应用设计了不同的抽象层次 状态化的流 抽象层次的最底层是状态化的流,它通过 ProcessFunction 嵌入到 ...

  6. 判断二叉树是否二叉排序树(BST)

    算法思想:由于二叉排序树的中序遍历可以得到一个有序的序列,因此,我们可以使用中序遍历进行求解. 代码如下: #include <stack> using namespace std; ty ...

  7. MTBF

    MTBF,即平均故障间隔时间,英文全称是"Mean Time Between Failure".是衡量一个产品(尤其是电器产品)的可靠性指标.单位为"小时".它 ...

  8. hi3531 SDK已编译文件系统制作jffs2文件系统镜像并解决问题 .

    一, 安装SDK 1.Hi3531 SDK包位置 在"Hi3531_V100R001***/01.software/board"目录下,您可以看到一个 Hi3531_SDK_Vx. ...

  9. Python中ValueError: invalid literal for int() with base 10 的实用解决办法

    爬虫代理IP由芝麻HTTP服务供应商提供今天在写爬虫程序的时候由于要翻页,做除法分页的时候出现了 totalCount = ' totalPage = int(totalCount)/20 Value ...

  10. hihocoder Challenge 29 D. 不上升序列

    这场爆零比赛题目还是要补的 这道题据说是出烂掉的原题,我找了下 CF13.C/ CF371 div1 C,一模一样 我服这群原题大佬 为 当时,使 不严格递增的最小步数,那么 Otherwise 显然 ...