这是在网上搜到的
在Java虚拟机里面提供了5条方法调用字节码指令,分别如下:
- invokestatic:调用静态方法。
- invokespecial:调用实例构造器方法、私有方法和父类方法。
- invokevirtual:调用所有的虚方法。还有final修饰的方法。
- invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
- invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而- - invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
方法重载
package com.bytecode;
public class MyTest5 {
public void test(Grandpa grandpa) {
System.out.println("grandpa");
}
public void test(Father father) {
System.out.println("father");
}
public void test(Son son) {
System.out.println("son");
}
public static void main(String[] args) {
Grandpa g1 = new Father();
Grandpa g2 = new Son();
MyTest5 myTest5 = new MyTest5();
myTest5.test(g1);
myTest5.test(g2);
}
}
class Grandpa {
}
class Father extends Grandpa {
}
class Son extends Father {
}
输出
grandpa
grandpa
对应Main方法的字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #7 // class com/bytecode/Father
3: dup
4: invokespecial #8 // Method com/bytecode/Father."<init>":()V
7: astore_1
8: new #9 // class com/bytecode/Son
11: dup
12: invokespecial #10 // Method com/bytecode/Son."<init>":()V
15: astore_2
16: new #11 // class com/bytecode/MyTest5
19: dup
20: invokespecial #12 // Method "<init>":()V
23: astore_3
24: aload_3
25: aload_1
26: invokevirtual #13 // Method test:(Lcom/bytecode/Grandpa;)V
29: aload_3
30: aload_2
31: invokevirtual #13 // Method test:(Lcom/bytecode/Grandpa;)V
34: return
LineNumberTable:
line 18: 0
line 19: 8
line 21: 16
line 23: 24
line 24: 29
line 25: 34
LocalVariableTable:
Start Length Slot Name Signature
0 35 0 args [Ljava/lang/String;
8 27 1 g1 Lcom/bytecode/Grandpa;
16 19 2 g2 Lcom/bytecode/Grandpa;
24 11 3 myTest5 Lcom/bytecode/MyTest5;
方法重写
package com.bytecode;
public class MyTest6 {
public static void main(String[] args) {
Fruit apple = new Apple();
Fruit orange = new Orange();
apple.test();
orange.test();
apple = new Orange();
apple.test();
}
}
class Fruit {
public void test() {
System.out.println("Fruit");
}
}
class Apple extends Fruit {
@Override
public void test() {
System.out.println("Apple");
}
}
class Orange extends Fruit {
@Override
public void test() {
System.out.println("Orange");
}
}
输出
Apple
Orange
Orange
主函数
public static void main(String[] args) {
Fruit apple = new Apple();
Fruit orange = new Orange();
apple.test();
orange.test();
apple = new Orange();
apple.test();
}
反编译
0: new #2 // class com/bytecode/Apple
3: dup
4: invokespecial #3 // Method com/bytecode/Apple."<init>":()V
7: astore_1
8: new #4 // class com/bytecode/Orange
11: dup
12: invokespecial #5 // Method com/bytecode/Orange."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method com/bytecode/Fruit.test:()V
20: aload_2
21: invokevirtual #6 // Method com/bytecode/Fruit.test:()V
24: new #4 // class com/bytecode/Orange
27: dup
28: invokespecial #5 // Method com/bytecode/Orange."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6 // Method com/bytecode/Fruit.test:()V
36: return
0: new (给它分配一个内存空间,生成Apple的实例,并将其引用值压入栈顶)
3: dup (复制操作数栈顶层的值,并把复制后的结果压入到操作数栈中)
4: invokespecial (调用Apple的构造方法)
7: astore_1 (将栈顶引用型数值存入索引为1的局部变量当中)
8: new (给它分配一个内存空间,生成Orange的实例,并将其引用值压入栈顶)
11: dup (复制操作数栈顶层的值,并把复制后的结果压入到操作数栈中)
12: invokespecial (调用Orange的构造方法)
15: astore_2 (将栈顶引用型数值存入索引为2的局部变量当中)
16: aload_1 (将局部变量表中索引为1的局部变量推送至栈顶)
17: invokevirtual
20: aload_2 (将局部变量表中索引为2的局部变量推送至栈顶)
21: invokevirtual
24: new (给它分配一个内存空间,生成Orange的实例,并将其引用值压入栈顶)
27: dup (复制操作数栈顶层的值,并把复制后的结果压入到操作数栈中)
28: invokespecial (调用Orange的构造方法)
31: astore_1 (将栈顶引用型数值存入索引为1的局部变量当中,之前那个相当于可以回收了)
32: aload_1 (从局部变量当中加载索引为1的引用apple)
33: invokevirtual
36: return
重载是静态分派,而重写是动态分派
-
静态分派
编译器在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,因此,在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本
-
动态分派
取决于方法的接受者(具体哪个真正的实例)
invokevirtual字节码指令涉及到多态查找的流程,具体流程是:
- 1、首先要操作数栈的栈顶去寻找到栈顶的元素所指向对象的实际类型。
上述例子中的代码 和 对应字节码,前面是有一个aload的操作将局部变量表中索引为1的局部变量推送至栈顶,因此栈顶元素一定是apple的实例
apple.test()
16: aload_1 (将局部变量表中索引为1的局部变量推送至栈顶)
17: invokevirtual
2、在该实际类型的对象当中,如果寻找到了与常量池中描述符和名称都相同的方法,并且具备相应的访问权限,就会直接返回目标方法的直接引用(就是Apple.test())
3、如果在实际类型的对象中没有找到该方法,那么就去其父类,继续执行该查找流程,直到找到,或者抛出异常
1、为什么上面的重载和重写的字节码中,都有invokevirtual的指令,这个指令不是只有动态分派才有的吗???
2、重载既然是静态行为,编译器就能确定。为什么用的是invokevirtual指令!
3、当我把那3个重载方法都改成了private的时候,字节码指令就变成了助记符invokespecial了。这又是为什么?重载到底影响invokevirtual指令吗?