Java多态知识点总结

多态

概述

我们继续使用继承中的的示例代码

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n4" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">public class Employee {
private String name;
private double salary;
private LocalDate hireDay;

public Employee(String name, double salary, int year, int month, int day) {
this.name = name;
this.salary = salary;
this.hireDay = LocalDate.of(year, month, day);
}

public String getName() {
return name;
}

public double getSalary() {
return salary;
}

public LocalDate getHireDay() {
return hireDay;
}

public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}

@Override
public String toString() {
return "Employee{" +
"name='" + name + ''' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
}</pre>

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n5" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">public class Manager extends Employee {
private double bonus;

public Manager(String name, double salary, int year, int month, int day) {
// Manager 类的构造器不能访问 Employee 类的私有域,所以必须利用 Employee 类的构造器对这部分私有域进行初始化, 且使用 super 调用构造器的语句必须是子类构造器的第一条语句。
super(name, salary, year, month, day);
this.bonus = 0;
}

public void setBonus(double bonus) {
this.bonus = bonus;
}

public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
}</pre>

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n7" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">public class ManagerTest {
public static void main(String[] args) {
Manager boss = new Manager("Jack", 8000, 2020, 4, 12);
boss.setBonus(2000);

Employee employee1 = boss;
Employee employee2 = new Employee("Alice", 2000, 2020, 4, 2);

System.out.println(employee1.getSalary());
System.out.println(employee2.getSalary());
}
}</pre>

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n8" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">// output
// 10000.0
// 2000.0</pre>

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n13" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">Employee e;
e = new Employee();
e = new Manager();</pre>

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n16" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">Employee e = new Employee();
Manager m = e; // error</pre>

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n33" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">// method table
Employee:
getName() -> Employee.getName()
getSalary() -> Employee.getSalary()
getHireDay() -> Employee.getHireDay()
raiseSalary() -> Employee.raiseSalary()

Manager:
getName() -> Employee.getName()
getSalary() -> Manager.getSalary()
getHireDay() -> Employee.getHireDay()
raiseSalary() -> Employee.raiseSalary()
setBonus() -> Manager.setBonus()</pre>

1.Java核心技术·卷 I(原书第10版)

Reference

  • 方法签名:方法名和参数列表,(f(int) 和f(String) :方法签名不同,但方法名相同)如果在子类中定义了和超类中签名相同的方法,此时会出现方法重写,但是在方法重写时一定要保证返回类型的兼容性,即允许子类将重写方法的返回类型定义为原返回类型的子类型。

  • 方法重写时子类方法不能低于超类方法的可见性。

注意:

  • 编译器获取方法签名 getSalary()

  • 由于该方法不是 private, static, final 或构造器方法,因此会采用动态绑定,虚拟机在运行时提取方法表

  • 虚拟机搜索 定义了 getSalary 签名的类,此时虚拟机已经知道调用哪个方法。

  • 虚拟机调用方法。

了解规则之后,我们可以看一下测试代码中 employee1.getSalary()的调用过程:

  • 编译器获取所有可能被调用的候选方法:编译器查看对象的声明类型(C)和方法名(f),此过程编译器会一一列举所有 C 类中名为 f 的方法和超类中访问属性为public 且名为 f 的方法。

  • 编译器获取需要调用的方法名字和参数类型:编译器会查看调用方法时提供的参数类型, 如果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配,就选择这个方法,这个过程被称为重载解析(overloading resolution)。由于允许类型转换(int 可以转换为 double, Manager 可以转换为 Employee等),此过程会很复杂,如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后存在多个方法与之匹配,就会报告一个错误。

  • 静态绑定(static bingding):如果方法为 private, static, final修饰的方法,或者是构造器方法,那么编译器可以准确地知道应该调用哪个方法。

  • 动态绑定(dynamic binding):不是静态绑定的方法都将采取动态绑定,调用的方法依赖与隐式参数的实际类型,在运行时实现动态绑定,此时虚拟久会调用与 x 所引用对象的实际类型最合适的那个类的方法:即假设 x 的实际类型为 D, D 为 C 的子类,且在 D 类中存在方法签名相同的方法,此时就会调用 D 中的方法,否则在 D 的超类中寻找该方法。

    • 由于每次调用方法都有进行搜索,时间开销很大,因此,虚拟机预先为每个类常见了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法,这样,在真正调用方法的时候,虚拟机仅查找这个表就行了。

我们假设现在调用 x.f(args) 隐式参数 x 声明为类 C 的一个对象,下面是调用过程的详细描述:

方法调用的过程

因为不是每个雇员(Employee)都是经理(Manager),不是 is-a 的关系,在代码层面也很好理解,如果这样赋值,很容易造成 m 调用 Manager 专属的方法引起错误。

但是需要主要注意不能将超类的引用复制给子类变量,如

在 Java 程序设计语言中,对象变量是多态的,一个 Employee 变量既可以引用一个 Employee 对象,也可以引用一个 Employee 类的任何一个子类的对象。

有一个用来判断是否应该设计为继承关系的简单规则,这就是 ”is-a“ 规则,它表明子类的每个对象也是超类的对象,例如上面的例子:每个 管理者(Manager)是雇员(Employee)。is-a 规则的另一种表述法是置换法则,它表明程序中出现超类对象的任何地方都可以用子类对象置换,如。

像上述情形,一个对象变量可以只是多种实际类型的现象称为多态(polymorphism),在运行时能够 自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)。

在上述测试代码中,我们可以看到声明为 Employee 的变量既可以引用 Employee 类型的对象,也可以引用 Manager 类型的对象,当引用 Employee 对象时调用其 getSalary方法, 当引用 Manager 对象时调用 Manager 对象的方法。

测试代码:

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

推荐阅读更多精彩内容