[讨论] 关于继承关系中的方法分派的疑惑
chenjingbo
2011-08-11
NightWatch 写道
静态分派根据引用类型来确定方法,这里调用到了父类的interestingMethod。
但是声明引用的时候是: Subclass me = new Subclass(); me.exampleMethod(); 引用类型应该是Subclass才对啊,是不是因为调用到 exampleMethod方法的时候内部的this引用类型变成了父类Super类型而不再是子类 Subclass类型呢?
你看下,引用类型是Sub类是对于exampleMethod方法来说的.这里只是确定了调用的是Super类中的 exampleMethod方法.而对于exampleMethod方法,它是通过静态分派来确定执行哪个interestingMethod方法的.很明显是执行Super类的interestingMethod方法. 只有对于虚方法,jvm才会尝试去使用动态分派. 虽然JAVA中大部分的方法都是虚方法. |
|
chenjingbo
2011-08-11
chenjingbo 写道
看到了.哈哈.
我是否可以这么理解.如果是 "invokespecial" 指令的,都是静态分派的.只要看常量池中对应哪个方法就会执行那个方法.而 "invokevirtual"指令的,都是需要根据 目标对象的具体类型来动态判断执行哪个方法呢?
public class StaticDispatch { static abstract class Human{ } static class Man extends Human{ } static class Woman extends Human{ } public void sayHello(Human human){ System.out.println("human say hello"); } public void sayHello(Man man){ System.out.println("man say hello"); } public void sayHello(Woman woman){ System.out.println("woman say hello"); } /** * @param args */ public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); StaticDispatch sd = new StaticDispatch(); sd.sayHello(man); sd.sayHello(woman); } }
可以看到,其实是一个很简单的overload.输出如下 console 写道
human say hello
human say hello
很明显,这个属于静态分派,或者说非虚方法分派.但是我们看对应的javap输出 javap 输出截取 写道
public static void main(java.lang.String[]);
Code: 0: new #7 // class StaticDispatch$Man 3: dup 4: invokespecial #8 // Method StaticDispatch$Man."<init> ":()V 7: astore_1 8: new #9 // class StaticDispatch$Woman 11: dup 12: invokespecial #10 // Method StaticDispatch$Woman."<ini t>":()V 15: astore_2 16: new #11 // class StaticDispatch 19: dup 20: invokespecial #12 // Method "<init>":()V 23: astore_3 24: aload_3 25: aload_1 26: invokevirtual #13 // Method sayHello:(LStaticDispatch$ Human;)V 29: aload_3 30: aload_2 31: invokevirtual #13 // Method sayHello:(LStaticDispatch$ Human;)V 34: return }
之前我特意google了 invokevirtual invokespecial两个指令.有些资料里有说法就是我之前说的 invokevirtual 指令对应动态分派,而invokespecial指令对应静态分派..刚才特意查了下,貌似又有新进展,新的说法是
写道
invokevirtual 是调用实例方法,也就是虚方法,而 invokespecial是调用超类构造方法,init方法和private方法
也就是说,其实这两个命令和JVM采用静态或者动态分派没有直接的联系(invokespecial指令肯定是静态分派算是联系不). 但是,还是有一个疑问就是:java的方法重载肯定采用静态分派是JVM的规范么??所有的资料都这么说,但是没解释出处是哪里.. 最后,撒迦大大,你挖的洞啥时候填啊..我等了一天了 |
|
RednaxelaFX
2011-08-11
你的问题的答案明明在我原本的那帖里就有。我明明写了:
RednaxelaFX 写道 Java虚拟机规范第二版中定义了四种不同的字节码指令来处理Java程序中不同种类的方法的调用。包括, · invokestatic - 用于调用类(静态)方法 · invokespecial - 用于调用实例方法,特化于super方法调用、private方法调用与构造器调用 · invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法) · invokeinterface - 用于调用接口方法 为啥没有看? 诶,有请IcyFenix同学来回答跟他的书相关的问题 |
|
chenjingbo
2011-08-11
那个坑啊..
引用 final但非private的实例方法还是用invokevirtual去调用的。虽然行为不是虚的。有别的原因导致这个设计。
为什么呢 |
|
RednaxelaFX
2011-08-11
chenjingbo 写道 那个坑啊..
引用 final但非private的实例方法还是用invokevirtual去调用的。虽然行为不是虚的。有别的原因导致这个设计。
为什么呢 直接把答案说出来就不有趣了。让我举个例子来诱导一下。 关键词:分离编译,二进制兼容性 A.java public class A { public void foo() { /* ... */ } } B.java public class B extends A { public void foo() { /* ... */ } } C.java public class C extends B { public final void foo() { /* ... */ } } 这样的话有3个源码文件,它们可以分别编译。三个类有继承关系,每个都有自己的foo()的实现。其中C.foo()是final的。 那么如果在别的什么地方, A a = getA(); a.foo(); 这个a.foo()应该使用invokevirtual是很直观的对吧? 而这个实际的调用目标也有可能是C.foo(),对吧? 所以为了设计的简单性,以及更好的二进制兼容性……(此处省略 |
|
chenjingbo
2011-08-11
我特意把final改成static..果然不行..
最后一个问题. 引用 java的方法重载肯定采用静态分派是JVM的规范么??所有的资料都这么说,但是没解释出处是哪里..
IcyFenix 同学的书中木有啊. |
|
wupuyuan
2011-08-31
这个其实在《深入JAVA虚拟机》中有解释。我们可以从两个角度来看。
1、如果一个方法是private的时候,它是不会重写的。 2、具体的可以通过javap来看,如果调用的是invokespecial调用的是private的方法(先绑定),如果是invokevirtual则调用的是public或者protected方法(后绑定),具体可以参考http://wupuyuan.iteye.com/blog/1157548 |
|
chenjingbo
2011-08-31
chenjingbo 写道 我特意把final改成static..果然不行..
最后一个问题. 引用 java的方法重载肯定采用静态分派是JVM的规范么??所有的资料都这么说,但是没解释出处是哪里..
IcyFenix 同学的书中木有啊. 嘿嘿,头段时间写总结的时候忽然悟到了..自己解释下. 引用 Java语言对重载采用静态分派的原因在于Java是动态单分派的!
在Java的方法重载中,每个方法的方法名是一样的,不同的地方在于它的方法参数类型和个数.但是在运行期,Java偏偏是不关心它的方法参数和个数的,它只关心对应的接受者!这也就是说,重载的方法分派在编译期就可以确定了,而不需要在运行期..既然在编译期已经确定,那么不就是静态分派么?? |