方法调用-解析
2012-12-29 23:14 fangzhao.lee 阅读(407) 评论(0) 编辑 收藏 举报方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.由于Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里存储的只是符号引用,而非方法在实际运行时内存布局中的入口地址.该特性为Java带来了更强大的动态扩展能力.
解析
所有方法调用中,目标方法在Class文件里都是一个常量池中的符号引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用--此情形的前提是:方法在程序实际运行前就有一个可确定的调用版本,且这个调用版本在运行期间是不可改变的.这类方法的调用成为解析(Resolution).
在Java语言中,符合"编译期可知,运行期不可变"要求的方法主要有静态方法和私有方法两大类,前者与类型相关联,后者在外部不可访问,这两种方法都不可能通过继承或别的方式重写出其他版本,因此它们都适合在类加载阶段进行解析.
与之相对应,在Java虚拟机中提供了四条方法调用字节码指令,分别是:
invokestatic 调用静态方法
invokespecial 调用实例构造器<init>方法,私有方法和父类方法
invokevirtual 调用所有虚方法
invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,符合此条件的有静态方法,私有方法,实例构造器和父类方法四类.它们在类加载时即把符号引用解析为该方法的直接引用.这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除final方法外).
以下代码中,静态方法sayHello()只可能属于类型StaticResolution,没有任何手段可以覆盖或隐藏该方法:
1 public class StaticResolution { 2 public static void sayHello() { 3 System.out.println("Hello world"); 4 } 5 6 public static void main(String[] args) { 7 StaticResolution.sayHello(); 8 } 9 }
使用javap命令查看这段程序的字节码,发现的确是通过invokestatic命令调用sayHello()方法的:
public static void main(java.lang.String[]);
Code:
Stack=0, Locals=1, Args_size=1
0: invokestatic #31; //Method sayHello:()V
3: return
LineNumberTable:
line 15: 0
line 16: 3
虽然final方法是使用invokevirtual指令来调用的,但由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,或者说多态选择的结果肯定是唯一的.在Java语言规范中明确说明了final方法是一种非虚方法.
读周志明的<深入理解Java虚拟机>一书第8.3章节, 备忘