一站式理解Java的动态绑定

Java的动态绑定之前一直一知半解,现在花一个晚上比较系统的掌握了下动态绑定的内容。

Override场景下的动态绑定

首先看下代码 Employee是基类 包含了name salary hireDate三个属性; Mannger继承了Employee 添加了bonus属性 setBonus()方法 并重写了getSalary()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package cn.deepblog.Test;

import java.util.Date;
import java.util.GregorianCalendar;

class Employee{

private String name;

private double salary;

private Date hireDate;

public Employee(String name, double salary, int year, int month, int day) {
this.name = name;
this.salary = salary;
GregorianCalendar ahireDate = new GregorianCalendar(year, month-1, day);
this.hireDate = ahireDate.getTime();
}

public String getName() {
return name;
}

public double getSalary() {
return salary;
}

public Date getHireDate() {
return hireDate;
}

public void raiseSalary(double byPercent){

double raise = salary * byPercent / 100;
salary += raise;
}

@Override
public String toString() {
return "name='" + name + '\'' +
", salary=" + salary +
", hireDate=" + hireDate;
}
}

class Mannger extends Employee{

private double bonus;

public Mannger(String name, double salary, int year, int month, int day) {
super(name, salary, year, month, day);
this.bonus = 0;
}

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

@Override
public double getSalary() {
return super.getSalary() + bonus;
}

@Override
public String toString() {
return super.toString() +
" bonus=" + bonus;
}
}

接下来看一个测试用例 新建一个Employee[]数组stuff stuff[0]是Manager实例对象boss stuff[1]和stuff[2]是Employee实例对象 foreach语句中打印了所有stuff的name与salary属性 那stuff[0]时 执行’e.getSalary()’调用的是Employee类中的’getSalary()’还是Manager类中的’getSalary()’呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class dynamicBindingTest {

public static void main(String[] args) {

Employee[] stuff = new Employee[3];

Mannger boss = new Mannger("Carl Cracker", 8000, 1987, 12, 15);
boss.setBonus(500);

stuff[0] = boss;
stuff[1] = new Employee("Hacker", 5000, 1989, 10, 1);
stuff[2] = new Employee("Tony", 4000, 1990, 11, 14);

for(Employee e : stuff){

System.out.println(e.getName() + " " + e.getSalary());
}
}
}

多态

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

1
2
3
Employee e1 = new Employee(...);

Employee e2 = new Mannger(...);

动态绑定–>涉及重写Override

对于private,final,static方法,编译器在编译器就可以知道其调用方式,这是静态绑定

调用的方法的方法依赖于隐式参数的实际类型,在运行期需要动态绑定

调用x.f(),x是D类的对象,C是D的父类,如果D类方法定义了f(),采用动态绑定调用方法时,就调用D类的f(),否则就在超类C中寻找f()

然后每次调用都搜索的话,开销大,所以虚拟机预先给每个类实现了一个方法表(key-value),key是该类的方法签名,value是调用key时实际应调用的方法。

1
所以暂且将"动态绑定"理解为自己没有,就去问父母要的过程。

变量多态的好处(冰山一角啦)

无论是Employee对象还是Employee子类的对象都可以使用Employee的对象来引用,例如stuff数组中元素类型不一样,但都属于Employee及其子类,所以可以统一使用e.getSalary()来得到不同对象的salary,即使Employee的子类重写了getSalary()也可以正确调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
Employee[] stuff = new Employee[3];

Mannger boss = new Mannger("Carl Cracker", 8000, 1987, 12, 15);
boss.setBonus(500);

stuff[0] = boss;
stuff[1] = new Employee("Hacker", 5000, 1989, 10, 1);
stuff[2] = new Employee("Tony", 4000, 1990, 11, 14);

for(Employee e : stuff){

System.out.println(e.getName() + " " + e.getSalary());
}

e.getSalary()调用过程

现在我们就理解了虚拟机动态绑定下e.getSalary()调用过程:

  • e的编译器类型是Employee, 运行期得到其实际类型为Mannger
  • 虚拟机提取e的实际类型Mannger的方法表
  • 在方法表中搜索到getSalary()
  • 调用Mannger类的getSalary()
1
2
3
注意:

"但是在动态绑定的过程中也可能会遇到形参类型不匹配的情况 此时也可以调用方法 这就是出现了重载解析的情况。"

重载解析–>涉及重载Overload

当调用c.f(param)时,编译器会列举c中所有名为f的方法以及c的父类中所有访问属性为public且名为f的方法,置此,编译器已经获得所有候选方法。

接下来编译器看f方法的形参,如果候选方法中存在与被调用方法形参完全匹配的,就直接调用(这就是重载解析)。由于运行类型转换的存在,所以若无解或多解则报错。

此处的类型转换就是向上转型,变量多态在重载中的表现在于形参的多态。

1
此处将"重载解析"理解为没有匹配的就将就一下的过程。(向上类型转换)

重载解析测试示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package cn.deepblog.Test;

class Person{

private String name = "person";

public String getName(Person person){

return person.name;
}
}

class Man extends Person{

private String name = "man";
}

class Coder extends Man{

private String name = "coder";
}

public class OverloadTest {

public static void main(String[] args) {

Person p = new Person();

System.out.println(p.getName(new Person()));

System.out.println(p.getName(new Man()));

System.out.println(p.getName(new Coder()));
}
}


输出:
person
person
person

解释一下 其中Person是Man的父类 Man是Coder的父类,Person类有一个参数是Person的getName()

当我们执行p.getName(new Person())完全匹配Person的getName()参数要求,输出person

当调用p.getName(new Man())参数是Man的实例对象,并且Person中也没有完全匹配的getName() 此时就会发生重载解析 new Man()被向上转型为Person对象,符合Person的getName()要求 完成调用输出person

当调用p.getName(new Coder())参数是Coder的实例对象,并且Person中也没有完全匹配的getName() 此时就会发生重载解析 new Coder()被向上转型为Man对象,依然不符合Person的getName()要求 继续向上转型为Person对象 符合Person的getName()要求 完成调用输出person

动态绑定与重载解析混合的测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package cn.deepblog.Test;

class Person{

private String name = "person";

public String getName(){

return name;
}

public String getName(Person person){

return "person: " + person.getName();
}
}


class Coder extends Person{

private String name = "coder";

@Override
public String getName(Person person){

return "coder: " + person.getName();
}
}

public class OverloadTest {

public static void main(String[] args) {

Person c = new Coder();

System.out.println(c.getName(new Person()));

System.out.println(c.getName(new Coder()));
}
}

输出:
coder: person
coder: person

解释一下:

  1. public String getName(Person person)是虚函数 需要动态绑定
  2. Person c = new Coder(); c编译期是Person类型 运行期的实际类型是Coder类型 –> 动态绑定

c.getName(new Person())的调用过程:

  • c的实际类型是Coder
  • 虚拟机搜索Coder的方法表 找到完全匹配的public String getName(Person person)
  • 执行方法 输出 “coder: person”

c.getName(new Coder())的调用过程:

  • c的实际类型是Coder
  • 虚拟机搜索Coder的方法表 未找到完全匹配的public String getName(Person person)
  • 将Coder向上转型为Person –> 重载解析
  • 找到完全匹配的public String getName(Person person)
  • 执行方法 输出 “coder: person”
Donate here.