方法调用
来源:互联网 发布:鉴知往来的意思 编辑:程序博客网 时间:2024/06/04 20:09
解析
所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析(Resolution)。
在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法和
私有方法两大类。
与之相对应的是,在Java虚拟机里面提供了5条方法调用字节码指令,分别如下。
1. invokestatic:调用静态方法。
2. invokespecial:调用实例构造器<init>方法、私有方法和父类方法。
3. invokevirtual:调用所有的虚方法。
4. invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
5. invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方
法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法。
Java中的非虚方法除了使用invokestatic、invokespecial调用的方法之外还有一种,就是被final修饰的方法。虽然final方法是使用invokevirtual指令来调用的,但是由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,又或者说多态选择的结果肯定是唯一的。在Java语言规范中明确说明了final方法是一种非虚方法。
分派
静态分派
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。另外,编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是“唯一的”,往往只能确定一个“更加合适的”版本。
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); }}
public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #7 // class com/fcs/jvms/StaticDispatch$Man 3: dup 4: invokespecial #8 // Method com/fcs/jvms/StaticDispatch$Man."<init>":( 7: astore_1 8: new #9 // class com/fcs/jvms/StaticDispatch$Woman 11: dup 12: invokespecial #10 // Method com/fcs/jvms/StaticDispatch$Woman."<init>")V 15: astore_2 16: new #11 // class com/fcs/jvms/StaticDispatch 19: dup 20: invokespecial #12 // Method "<init>":()V 23: astore_3 24: aload_3 25: aload_1 26: invokevirtual #13 // Method sayHello:(Lcom/fcs/jvms/StaticDispatch$Hum;)V 29: aload_3 30: aload_2 31: invokevirtual #13 // Method sayHello:(Lcom/fcs/jvms/StaticDispatch$Hum;)V 34: return LineNumberTable: line 36: 0 line 37: 8 line 38: 16 line 39: 24 line 40: 29 line 41: 34 LocalVariableTable: Start Length Slot Name Signature 0 35 0 args [Ljava/lang/String; 8 27 1 man Lcom/fcs/jvms/StaticDispatch$Human; 16 19 2 woman Lcom/fcs/jvms/StaticDispatch$Human; 24 11 3 staticDispatch Lcom/fcs/jvms/StaticDispatch;
动态分派
它和多态性的另外一个重要体现——重写/覆盖(Override)有着很密切的关联。
public class DynamicDispatch { static abstract class Human{ protected abstract void sayHello(); } static class Man extends Human{ @Override protected void sayHello() { System.out.println("man say hello"); } } static class Woman extends Human{ @Override protected void sayHello() { System.out.println("woman say hello"); } } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); man.sayHello(); woman.sayHello(); man = new Woman(); man.sayHello(); }}
查看字节码,编译阶段看不出任何区别
public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: new #2 // class com/fcs/jvms/DynamicDispatch$Man 3: dup 4: invokespecial #3 // Method com/fcs/jvms/DynamicDispatch$Man."<init>":()V 7: astore_1 8: new #4 // class com/fcs/jvms/DynamicDispatch$Woman 11: dup 12: invokespecial #5 // Method com/fcs/jvms/DynamicDispatch$Woman."<init>":()V 15: astore_2 16: aload_1 17: invokevirtual #6 // Method com/fcs/jvms/DynamicDispatch$Human.sayHello:()V 20: aload_2 21: invokevirtual #6 // Method com/fcs/jvms/DynamicDispatch$Human.sayHello:()V 24: new #4 // class com/fcs/jvms/DynamicDispatch$Woman 27: dup 28: invokespecial #5 // Method com/fcs/jvms/DynamicDispatch$Woman."<init>":()V 31: astore_1 32: aload_1 33: invokevirtual #6 // Method com/fcs/jvms/DynamicDispatch$Human.sayHello:()V 36: return LineNumberTable: line 30: 0 line 31: 8 line 32: 16 line 33: 20 line 34: 24 line 35: 32 line 36: 36 LocalVariableTable: Start Length Slot Name Signature 0 37 0 args [Ljava/lang/String; 8 29 1 man Lcom/fcs/jvms/DynamicDispatch$Human; 16 21 2 woman Lcom/fcs/jvms/DynamicDispatch$Human;
invokevirtual指令的运行时解析过程大致分为以下几个步骤:
找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校
验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回
java.lang.IllegalAccessError异常。否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是Java语言中方法重写的本质。
单分派与多分派
Java语言是一门静态多分派、动态单分派的语言。
虚拟机动态分派的实现
- 方法调用
- 方法调用
- 方法调用
- 方法调用
- 调用方法
- 方法调用
- 方法调用
- 调用方法
- 20171114 方法调用方法
- 动态调用方法 延时调用方法
- js调用方法中的方法
- 重载方法调用。
- JavaRMI远程方法调用
- C#调用DLL方法
- 动态调用方法实例
- 方法的调用示例
- 循环调用的方法
- Java远程方法调用
- 17年10月自考--一直在路上~
- HDU 4417 Super Mario(线段树离线处理/主席树)
- C语言(一)C语言格式
- 从Xutils运行时注解复习Java注解
- 有关时间复杂度的计算
- 方法调用
- angular中的value、factory、service、constent
- 出栈顺序问题
- LuoGu 1002 过河卒
- 记SQL语句中的as和is的区别
- Mac笔记本常用快捷键
- jvm学习笔记--运行时数据区域
- 学习机器学习之如何根据需求选择一种算法
- 477. Total Hamming Distance(C++)