重载和重写是分派中的两个重要体现,也是因为这个原因我们才把重载和重写写在了标题上。这一章我们的很多部分都在代码试验上。
总的来说分派分为静态分派和动态分派两种。
静态分派:
首先我们来看一段源码:
public class Dispatch { public static void main(String[] args){ Animal a = new Dog(); Animal b = new Cat(); sound(a); sound(b); } public static void sound(Animal a){ System.out.println("...."); } public static void sound(Dog a){ System.out.println("wang.."); } public static void sound(Cat a){ System.out.println("miao.."); } static class Animal{ } static class Dog extends Animal{ } static class Cat extends Animal{ } }
这段源码就是写了三个重载的方法,根据参数的不同输出不同的信息。应该大家都能知道正确输出,输出如下:
.... ....
Animal a = new Dog();在这个里面我们把Animal叫做外观类型(静态类型)把Dog叫做实际类型。静态类型是在编译期可知的,但是一个对象的实际类型要在运行期才可知。而jvm在重载的时候通过参数的静态类型作为判定依据。我们来看一下字节码是不是和我们想的一样
看到图中invokestatic的字符引用,我们应该就知道了编译时期确实完成了方法的定位。
而这些需要靠静态类型定位方法的情形称为静态分派,静态分派发生在编译阶段。也就是说在这个方法执行之前仅仅在编译阶段,sound方法就认定了最后要输出一个"...."而不是wang和miao。
动态分派:
我们依然先给一段代码:
public class Dispatch { public static void main(String[] args){ Animal a = new Dog(); Animal b = new Cat(); a.sound(); b.sound(); } static class Animal{ public void sound(){ System.out.println("......"); } } static class Dog extends Animal{ public void sound(){ System.out.println("wang!"); } } static class Cat extends Animal{ public void sound(){ System.out.println("miao!"); } } }
这一段代码的输出结果相信我不用贴大家都明白。
wang! miao!
但是,为什么jvm能够找到这一段方法并且执行的呢?
我们来看一下javap的输出信息
图中画红线的两个部分就是编译后的字节码文件了,他们调用的依然是animal的sound方法,看来动态分派不是在编译期完成的。问题就出在了invokevirtual上面了
jvm运行invokevirtual方法有一个过程,大致如下。
1、找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
2、如果C与常量中的描述符和简单名称都符合的方法,说明这个方法就是我们找的,只需要进行访问权限校验(看看是不是private之类)。
3、如果不符合描述符或简单名称。则对C的父类进行第二步中的搜索。
4、最终都没有找到抛出AbstractMethodError。
可能第3步比较难懂,我们再来仔细说明一下
如果我们把代码修改如下:
public class Dispatch { public static void main(String[] args){ Animal a = new Dog(); Animal b = new Cat(); a.sound(); b.sound(); } static class Animal{ public void sound(){ System.out.println("......"); } } static class Dog extends Animal{ public void sound(String a){//修改后的方法 System.out.println("wang!"); } } static class Cat extends Animal{ public void sound(){ System.out.println("miao!"); } } }
那么很明显上述中的C在Animal a = new Dog();中就是Dog类,Dog类中只有一个sound(string)的方法并不存在一个sound()方法,所以去C的父类中即Animal类中找到sound方法,最终输出如下:
...... miao!