概念
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不设计方法内部的具体运行过程。在程序运行时,进行方法调用是最普遍、最频繁的操作,Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相对于之前说的直接引用)。这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。
解析
所有丰富的调用中的目标方法在Class文件里面都是一个常量池的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是:**方法在程序真正运行之前就有一个可确定的调用版本,并且方法的调用版本在运行期间是不可改变的。**换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析(Resolution)。
在Java语言中符合“编译期可知,运行期不可变”,这个要求的方法,主要包括静态方法和私有方法量大类,前者与类型直接相关,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能通过继承或别的方式重写其他版本。
public class StaticResolution {
public static void sayHello() {
System.out.println("hello world");
}
public static void main(String[] args) {
StaticResolution.sayHello();
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #5 // Method sayHello:()V
3: return
LineNumberTable:
line 18: 0
line 19: 3
解析调用一定是一个静态的过程,在编译期间就完全确定,在类装载的解析阶段就会把涉及到的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。而分派(Dispatch)调用则可能是静态的也可能是动态的,根据分派一句的宗量数可分为单分派和多分派。这两类分派方式的两两组合就构成了静态单分派、静态多分派、动态单分派、动态多分派这四种情况。
分派
Java是一门面向对象的程序语言,因为Java具备面向对象的3个基本特征:继承、封装、多态。分派调用过程将会揭示多态性特征的一些最基本的体验,如“重载”和“重写”在Java虚拟机如何实现的。
静态分派
所以依赖静态类型来定位方法执行版本的分配动作称为静态分派。静态分派的典型应用就是方法的重载。在重载时是通过参数的静态类型而不是实际类型作为判断依据的。
在这段代码中,创建的对象如:man和women的静态类型为Human,变量的实际类型为Man和Women。方法的接受者依据确定是对象’sr’
代码:
public class Overload {
public static void sayHello(Object args) {
System.out.println("hello Object");
}
public static void sayHello(int args) {
System.out.println("hello int");
}
public static void sayHello(long args) {
System.out.println("hello long");
}
public static void sayHello(char args) {
System.out.println("hello char");
}
public static void sayHello(char... args) {
System.out.println("hello char ...");
}
public static void sayHello(Serializable args) {
System.out.println("hello Serializable");
}
public static void main(String[] args) {
sayHello('a');
}
}
上面代码输出:
hello char
这很好理解,'a’是一个char类型的数据,自然回去寻找参数类型为char的重载方法,如果这里注释掉sayHello(char args)方法,那么输出:
hello int
这里发生了一次自动类型转换,‘a’除了可以代表一个字符串,还可以代表97。因此参数类型为int的重载也是合适的,继续注释掉int方法后,输出为long:
hello long
这里发生了两次自动类型转换,'a’转型为整数97之后,进一步转型为长整型97L,匹配了参数类型为long的重载
。。。。。。
char——>int——>long——>Serializeable——>Object——>Char…
动态分派
动态分派与多态的另一个重要体现——重写(Override)有着很密切的关联。
public class DynamicDispatch {
static abstract class Human {
protected abstract void syaHello();
}
static class Man extends Human {
@Override
protected void syaHello() {
System.out.println("man say hello");
}
}
static class Women extends Human {
@Override
protected void syaHello() {
System.out.println("women say hello");
}
}
public static void main(String[] args) {
Human man = new Man();
Human women = new Women();
man.syaHello();
women.syaHello();
man = new Women();
man.syaHello();
}
}
输出结构
man say hello
women say hello
women say hello
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class com/vma/analysis/DynamicD
ispatch$Man
3: dup
4: invokespecial #3 // Method com/vma/analysis/Dynamic
Dispatch$Man."<init>":()V
7: astore_1
8: new #4 // class com/vma/analysis/DynamicD
ispatch$Women
11: dup
12: invokespecial #5 // Method com/vma/analysis/Dynamic
Dispatch$Women."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method com/vma/analysis/Dynamic
Dispatch$Human.syaHello:()V
20: aload_2
21: invokevirtual #6 // Method com/vma/analysis/Dynamic
Dispatch$Human.syaHello:()V
24: new #4 // class com/vma/analysis/DynamicD
ispatch$Women
27: dup
28: invokespecial #5 // Method com/vma/analysis/Dynamic
Dispatch$Women."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6 // Method com/vma/analysis/Dynamic
Dispatch$Human.syaHello:()V
36: return
LineNumberTable:
line 32: 0
line 33: 8
line 34: 16
line 35: 20
line 36: 24
line 37: 32
line 38: 36