JVM学习笔记——Java和JVM在重写语义上的差异
Java和JVM中识别方法的差异
Java在其语义中,通过类名、方法名和方法的参数类型唯一标识方法。
JVM通过类名、方法名和方法描述符(方法的参数类型以及方法的返回类型)唯一标识方法。
差异显而易见。在Java中,若同类、同方法名以及方法参数类型相同,则无法通过编译;而在JVM中还可以通过方法的返回类型区分方法。
Java和JVM中重写的差异
重写的判定基于Java和JVM中识别方法的方式,因此在Java和JVM中的重写语义不相同。如下述代码中,在Java中,DifferenceInOverriding中"invoke"是父类中"invoke"的重写方法,但在JVM中则相反。
public class DifferenceInOverridingSuper {
public Number invoke() {
return null;
}
}
public class DifferenceInOverriding extends DifferenceInOverridingSuper {
@Override
public Float invoke() {
return null;
}
}
Java和JVM的重写语义不一致,为了统一差异,编译器生成桥接方法来实现Java中的重写语义。为证实这一论点,可编译上述代码,查看桥接方法:
# 编译
javac DifferenceInOverriding.java
# 反编译
javap -v DifferenceInOverriding > difference
vi difference
可以看到两个标明为invoke()的方法,但其中为一个为桥接方法:
public java.lang.Float invoke();
descriptor: ()Ljava/lang/Float;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aconst_null
1: areturn
LineNumberTable:
line 4: 0
public java.lang.Number invoke();
descriptor: ()Ljava/lang/Number;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC // ACC_BRIDGE即说明为桥接方法
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #2 // Method invoke:()Ljava/lang/Float;
4: areturn
LineNumberTable:
line 1: 0
可以看到一行"1: invokevirtual #2 // Method invoke:()Ljava/lang/Float;",为了证实此处的注释,可翻到该文件头部的常量池(摘取部分):
Constant pool:
#1 = Methodref #4.#14 // DifferenceInOverridingSuper."<init>":()V
#2 = Methodref #3.#15 // DifferenceInOverriding.invoke:()Ljava/lang/Float;
#3 = Class #16 // DifferenceInOverriding
#9 = Utf8 invoke
#10 = Utf8 ()Ljava/lang/Float;
#15 = NameAndType #9:#10 // invoke:()Ljava/lang/Float;
#16 = Utf8 DifferenceInOverriding
可以看到这里的“#2”符号引用指向了"#3.#15",解析得调用了返回Float类型的DifferenceInOverriding#invoke,可理解为:
return this.invoke();
总结
Java和JVM中重写的前提是子类定义了父类中非私有、非静态的同名方法。除了Java中的类名、方法名和方法的参数类型,JVM还可以通过方法返回类型来标识一个方法,由此Java和JVM重写语义不一致,编译器生成桥接方法来统一语义上的差异。