另外一种退出方式是在方法执行的过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理。这种退出方法的方式称为“异常调用完成(Abrupt Method Invocation Completion)”。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者提供任何返回值的。
无论采用何种退出方式,在方法退出之后,都必须返回到最初方法被调用时的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。
方法正常退出时,主调方法的PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存这部分信息。
一般会把动态连接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息。
方法调用
方法调用并不等同于方法中的代码被执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还未涉及方法内部的具体运行过程。
解析
所有方法调用的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将符合“编译期可知,运行期不可变”的方法符号引用转化为直接引用。换句话说,调用目标在程序代码写好、编译器进行编译那一刻就已经确定下来。这类方法的调用被称为解析(Resolution)。
静态方法、私有方法、实例构造器、父类方法4种,再加上被final修饰的方法(尽管它使用invokevirtual指令调用),这5种方法调用会在类加载的时候就可以把符号引用解析为该方法的直接引用。这些方法统称为“非虚方法”(Non-VirtualMethod),与之相反,其他方法就被称为“虚方法”(Virtual Method)。
分派Dispatch
1.静态分派
英文一般是“Method Overload Resolution”,所以其实是个动态概念
public class StaticDispatch { static abstract class Human{ } static class Man extends Human{ } static class Woman extends Human{ } public void sayHello(Human guy){ System.out.println("Hello guy"); } public void sayHello(Man guy){ System.out.println("Hello gentleman"); } public void sayHello(Woman guy){ System.out.println("Hello lady"); } public static void main(String[] args){ Human man = new Man(); Human woman = new Woman(); StaticDispatch staticDispatch = new StaticDispatch(); staticDispatch.sayHello(man); staticDispatch.sayHello(woman); }}运行结果Hello guyHello guyHuman hu = new Man():
面代码中的“Human”称为变量的静态类型(Static Type)或者外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是编译期可知的;而实际类型变化的结果在运行期才可确定,编译期在编译程序的时候并不知道一个对象的实际类型是什么?如下面的代码:
//实际类型变化Human man = new Man();man = new Woman();//静态类型变化sd.sayHello((Man)man);sd.sayHello((Woman)man);
所有依赖静态类型来决定方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用表现就是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的,这点也是为何一些资料选择把它归入“解析”而不是“分派”的原因。
public class StaticPaiDemo { public static void sayHello(int i){ System.out.println("int 类型"); } public static void sayHello(Object obj){ System.out.println("obj 类型"); } public static void sayHello(long i){ System.out.println("long 类型"); } public static void sayHello(char i){ System.out.println("char 类型"); } public static void main(String[] args) { sayHello(1); sayHello(1L); sayHello('a'); }}