JAVA:一篇文章理清多态

很多人总是喜欢,或者说错误地将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()返回的是15salary,对b调用getAnnualSalary()却返回了13salary,好像的确是所谓“方法的多态”,毕竟对同一类对象变量调用同一个方法,内部实现方式却出现不同了嘛。基于这样的想法,甚至有一些人将多态扩展到了更广泛、更复杂的情况,比如下面这种,连泛型都算进了多态中:

那么,多态真的是有那么多种情况吗?真的是只要方法名相同,而参数或者内部实现方式不同,就要看成是多态吗?不不不,这种说法纯属扯淡,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返回12salary,Enginner类重写了该方法以返回15salary,那么当出现下述情况时:

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多态时最重要的点就是要明白多态就是指对象变量的多态,不要去把多态这个概念复杂化。至于所谓“方法的多态”,其实就是方法调用的静态绑定(筛选)和动态绑定。

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,107评论 1 32
  • 父类与子类 在Java术语中,如果C1类扩展自另一个类C2,我们称C2为父类,也称超类或基类,称C1为子类,也称次...
    Steven1997阅读 1,170评论 1 2
  • 5继承 5.1 类、超类和子类 重用部分代码,并保留所有域。“is-a”关系,用extends表示。 已存在的类被...
    我快要上天啦阅读 785评论 1 3
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,418评论 1 14
  • 今天下午,最后的一节课,我们发赏识卡,我们举行了一个活动,那就是谁的赏识卡最多。我觉得老师为什么要发给...
    刘俊艳阅读 715评论 0 0