JVM基础 -- 方法重载 + 方法重写

本文将从JVM字节码的角度解释方法重载方法重写

方法重载

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package me.zhongmingmao.test;

public class StaticDispatch {
interface Human {
}

static class Man implements Human {
}

static class Woman implements Human {
}

public static void sayHello(Human human) {
System.out.println("Hello , I'm a human");
}

public static void sayHello(Man man) {
System.out.println("Hello , I'm a man");
}

public static void sayHello(Woman woman) {
System.out.println("Hello , I'm a woman");
}

public static void main(String[] args) {
Human man = new Man(); // 静态类型是Human,实际类型是Man
Human woman = new Woman(); // 静态类型是Human,实际类型是Woman

sayHello(man); // 静态类型是Human,实际类型是Man
sayHello(woman); // 静态类型是Human,实际类型是Woman
sayHello((Man) man); // 静态类型是Man,实际类型是Man
sayHello((Woman) woman); // 静态类型是Woman,实际类型是Woman
}
}

运行结果

1
2
3
4
Hello , I'm a human
Hello , I'm a human
Hello , I'm a man
Hello , I'm a woman

分析

静态类型 VS 实际类型

  1. 静态类型编译期可知,实际类型需要到运行期才可确定
  2. 例如sayHello(Human human),编译期只知道传进来的参数是Human类型的实例,但只有到了运行期才知道实际传进来的是Man类型实例还是Woman类型实例(或者其他Human实现类的实例)

重载判定

  1. 编译期重载时是通过参数的静态类型而不是实际类型作为判断依据
  2. 因此在编译期会依据参数的静态类型来决定使用哪一个重载版本

字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# javap -v
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 #7 // class me/zhongmingmao/test/StaticDispatch$Man
3: dup
4: invokespecial #8 // Method me/zhongmingmao/test/StaticDispatch$Man."<init>":()V
7: astore_1
8: new #9 // class me/zhongmingmao/test/StaticDispatch$Woman
11: dup
12: invokespecial #10 // Method me/zhongmingmao/test/StaticDispatch$Woman."<init>":()V
15: astore_2
16: aload_1
17: invokestatic #11 // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Human;)V
20: aload_2
21: invokestatic #11 // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Human;)V
24: aload_1
25: checkcast #7 // class me/zhongmingmao/test/StaticDispatch$Man
28: invokestatic #12 // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Man;)V
31: aload_2
32: checkcast #9 // class me/zhongmingmao/test/StaticDispatch$Woman
35: invokestatic #13 // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Woman;)V
38: return
LocalVariableTable:
Start Length Slot Name Signature
0 39 0 args [Ljava/lang/String;
8 31 1 man Lme/zhongmingmao/test/StaticDispatch$Human;
16 23 2 woman Lme/zhongmingmao/test/StaticDispatch$Human;

其中4个调用sayHello方法JVM字节码指令

1
2
3
4
17: invokestatic  #11   // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Human;)V
21: invokestatic #11 // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Human;)V
28: invokestatic #12 // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Man;)V
35: invokestatic #13 // Method sayHello:(Lme/zhongmingmao/test/StaticDispatch$Woman;)V

从字节码可以看出,在编译期,依据按照参数的静态类型已经明确选择了调用哪一个重载版本

方法重写

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package me.zhongmingmao.test;

public class DynamicDispatch {

interface Human {
default void sayHello() {
System.out.println("Hello , I'm a human");
}
}

static class Man implements Human {
@Override
public void sayHello() {
System.out.println("Hello , I'm a man");
}
}

static class Woman implements Human {
@Override
public void sayHello() {
System.out.println("Hello , I'm a woman");
}
}

public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();

man.sayHello();
woman.sayHello();

man = new Woman();
man.sayHello();
}
}

运行结果

1
2
3
Hello , I'm a man
Hello , I'm a woman
Hello , I'm a woman

分析

字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 me/zhongmingmao/test/DynamicDispatch$Man
3: dup
4: invokespecial #3 // Method me/zhongmingmao/test/DynamicDispatch$Man."<init>":()V
7: astore_1
8: new #4 // class me/zhongmingmao/test/DynamicDispatch$Woman
11: dup
12: invokespecial #5 // Method me/zhongmingmao/test/DynamicDispatch$Woman."<init>":()V
15: astore_2
16: aload_1
17: invokeinterface #6, 1 // InterfaceMethod me/zhongmingmao/test/DynamicDispatch$Human.sayHello:()V
22: aload_2
23: invokeinterface #6, 1 // InterfaceMethod me/zhongmingmao/test/DynamicDispatch$Human.sayHello:()V
28: new #4 // class me/zhongmingmao/test/DynamicDispatch$Woman
31: dup
32: invokespecial #5 // Method me/zhongmingmao/test/DynamicDispatch$Woman."<init>":()V
35: astore_1
36: aload_1
37: invokeinterface #6, 1 // InterfaceMethod me/zhongmingmao/test/DynamicDispatch$Human.sayHello:()V
42: return
LocalVariableTable:
Start Length Slot Name Signature
0 43 0 args [Ljava/lang/String;
8 35 1 man Lme/zhongmingmao/test/DynamicDispatch$Human;
16 27 2 woman Lme/zhongmingmao/test/DynamicDispatch$Human;

关于字节码的执行过程请参考博文「字节码 - JVM字节码执行过程」,下面仅做简略说明

创建对象

指令0347的主要作用是创建并初始化一个Man实例,并存入局部变量表中偏移为1的slot中(指令8111215作用类似)

  1. 0: new :为Man类型分配内存,并将引入压入操作数栈
  2. 3: dup复制栈顶元素(即刚刚创建的Man引用),再次入栈,此时栈有2个一样的Man引用,主要是为了后面有2个出栈操作
  3. 4: invokespecial弹出栈顶元素,即Man引用,调用<init>方法(即JVM为我们生成的合成构造器方法)
  4. 7: astore_1弹出栈顶元素,同样也是Man引用,并将其存入局部变量表中偏移为1的slot
  5. 15: astore_2 :这条指令执行完以后,局部变量表中偏移为1的slot中存储的是man实例的引用偏移为2的slot中存储的是woman实例的引用

多态查找

这里仅分析第一个man.sayHello();,其他的都是类似的原理

  1. 16: aload_1 : 将局部变量表中偏移为1的slot中的值压入到操作数栈中,此时栈顶元素man实例的引用
  2. 17: invokeinterface :弹出栈顶元素,并调用接口方法(中间还有校验等步骤,这里忽略),即调用Man类型的实现

JVM字节码指令的执行伴随着操作数栈的出栈和入栈操作多态调用也是在运行期才能确定调用的是哪一个重写版本

0%