[讨论] 关于继承关系中的方法分派的疑惑

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"指令的,都是需要根据 目标对象的具体类型来动态判断执行哪个方法呢?


貌似我这里说的有个错误呀.第一句,"如果是 'invokespecial' 指令的,都是静态分派的"这句话貌似还没找到错误的地方(虽然感觉好像不是很严谨) .后面那句 "'invokevirtual'指令的,都是需要根据 目标对象的具体类型来动态判断执行哪个方法" 这句是错误的.例子如下(这个例子摘自<深入Java虚拟机-JVM高级特性与最佳实践>)

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偏偏是不关心它的方法参数和个数的,它只关心对应的接受者!这也就是说,重载的方法分派在编译期就可以确定了,而不需要在运行期..既然在编译期已经确定,那么不就是静态分派么??
Global site tag (gtag.js) - Google Analytics