分派
静态分派
所有依赖静态类型来决定方法执行版本的分派动作,都成为静态分派。
静态分配的最典型应用表现就是方法重载。
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
例子
1 | public class Test { |
结果
我是鸟!
我是鸟!
分析

理解这个要明白重载是根据
静态类型而不是实际类型来判断选择哪个方法的。Bird sparrow = new Sparrow();
这个Bird 称为变量的 “静态类型”,而Sparrow 则是 变量的 “实际类型”。变量本身的静态类型是不会发生变化的,在编译期是可知的,上图也说明了这一点,程序还未运行便知道要调用的方法。实际类型变化的结果是在运行期间才可以确定的。编译器在编译程序的时候并不知道一个对象的实际类型是什么。
问题
1 | public class Test { |
这个代码输出什么?

分析
这说明了重载的版本并不是唯一的,但是往往必须确定一个“相对更加合适的”版本。
‘a’是char类型,就去寻找char类型的重载方法,如果此方法不存在,他就会自动进行类型转换’a’也可以代表数字97。所以他就会去执行int类型的重载方法了。
动态分派
- 在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
例子
1 | public class Test { |
结果
我是鸟中的麻雀!
我是鸟中的喜鹊!
我是鸟中的喜鹊!
分析
显然重写不是根据静态类型确定调用哪个方法的,而是根据实际类型。子类重写了父类的方法不同的子类必然产生的动作不同,这肯定就不能依靠静态类型来确定了。

分析main()方法中的字节码:

invokevirtual
运行时解析过程
- 找到操作数栈顶的第一个元素所指向的对象的实际类型,记做C
- 如果在类型C中找到与常量中描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。
- 否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索验证过程。
- 如果始终没有找到合适的方法,则抛出java.lang.AbstartMethodError异常。
这个说的很明白了,第1步不就找到实际类型吗!!这个过程就是Java语言中方法重写的本质。
既然这种多态性的根源是和invokevirtual这个指令有关,那么这个只能对方法生效对字段是无效的,因为字段并不使用这个指令,字段永远没有多态这个特性至少目前还没有(jdk14)。
彻底理解多态
1 | class Test { |
分析
解析:
执行对象实例化过程中遵循多态特性,调用的方法都是将要实例化的子类中的重写方法,只有明确调用了super.xxx关键词或者是子类中没有该方法时,才会去调用父类相同的同名方法。
- new B()构造一个B类的实例
- B的构造函数中super(5)中显示调用父类A的构造函数
- 执行A (int v) => setValue(v)
- 虽然构造函数是A类的构造函数,但此刻正在初始化的对象是B的一个实例,因此这里调用的实际是B类的setValue方法,于是调用B类中的setValue方法,而B类中setValue方法显示调用父类的setValue方法,将B实例的value值设置为2 x 5 = 10。
- 至此super(5)这条语句执行完成,紧接着执行setValue(getValue() - 3)
- 由于B类中没有重写getValue方法,因此调用父类A的getValue方法。
- value++ 此时B的成员变量value=11,11这个返回值会先暂存起来,return value,跳过,先执行finally中的方法。
- this.setValue(value);调用的是B类的setValue方法,因为此刻正在初始化的是B类的一个对象(运行时多态),然后super.setValue(11 * 2)这里显示调用A类的setValue方法,将B的value设置为了22
- 然后System.out.println(value) 因此第一个打印的值为22。
- finally语句执行完毕,会把刚才暂存的11返回出去,也就是说这么经历了这些处理,getValue方法最终的返回值是11。
- setValue(11 - 3) => setValue(8)
- 执行setValue(8)执行的肯定是B类的setValue方法,然后value就变成了16。
- 到此new B()构造完毕
- 然后执行new B().getValue()方法,B中不存在此方法,所以调用的是A类的此方法。
- value++,B的成员变量value值为17,此时执行到return语句,先暂存,然后执行finally中语句,和之前原理一样,打印出34。
- 然后把value = 17返回出去,导致System.out.println(new B().getValue)就打印出17
- 所以最终的打印结果就是22 34 17



