原文
回顾Java语言中的重载与重写,并且看看JVM是怎么处理它们的。
定义:
JVM是怎么处理重载的?其实是编译阶段编译器就已经决定好调用哪一个重载方法。看下面代码:
class Overload { void invoke(Object obj, Object... args) { } void invoke(String s, Object obj, Object... args) { } void test1() { // 调用第二个 invoke 方法 invoke(null, 1); } void test2() { // 调用第二个 invoke 方法 invoke(null, 1, 2); } void test3() { // 只有手动绕开可变长参数的语法糖,才能调用第一个invoke方法 invoke(null, new Object[]{1}); } }
上面的注释告诉了我们结果,那么怎么才能证明上面的注释呢?我们利用 javap
观察字节码可以知道。
$ javac Overload.java $ javap -c Overload.java Compiled from "Overload.java" class Overload { ... void invoke(java.lang.Object, java.lang.Object...); Code: 0: return void invoke(java.lang.String, java.lang.Object, java.lang.Object...); Code: 0: return void test1(); Code: ... 10: invokevirtual #4 // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V 13: return void test2(); Code: ... 17: invokevirtual #4 // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V 20: return void test3(); Code: ... 13: invokevirtual #5 // Method invoke:(Ljava/lang/Object;[Ljava/lang/Object;)V 16: return }
这里面有很多JVM指令,你暂且不用关心,我们看 test1
、 test2
、 test3
方法调用的是哪个方法:
void test1(); Code: ... 10: invokevirtual #4 // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V 13: return
invoke
是方法名, (Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V
则是方法描述符。这里翻译过来就是 void invoke(String, Object, Object[])
,Java的可变长参数实际上就是数组,所以等同于 void invoke(String, Object, Object...)
。同理, test2
调用的是 void invoke(String, Object, Object...)
, test3
调用的是 void invoke(Object, Object...)
。关于方法描述符的详参 JVM Spec - 4.3.2. Field Descriptors 和 JVM Spec - 4.3.3. Method Descriptors 。
所以重载方法的选择是在编译过程中就已经决定的,下面是编译器的匹配步骤:
如果在一个步骤里匹配到了多个方法,则根据形参类型来找最贴切的。在上面的例子中第一个 invoke
的参数是 Object, Object...
,第二个 invoke
的参数是 String, Object, Object...
,两个方法的第一个参数 String
是 Object
的子类,因此更为贴切,所以 invoke(null, 1, 2)
会匹配到第二个 invoke
方法上。
Java语言中的定义:
父类方法的返回值可以替换掉子类方法的返回值。也就是说父类方法的返回值类型:
(更多详细信息可参考 Java Language Spec - 8.4.8. Inheritance, Overriding, and Hiding ,这里除了有更精确详细的重写的定义,同时包含了范型方法的重写定义。)
但是JVM中对于重写的定义则有点不同:
(更多详细信息可参考 JVM Spec - 5.4.5. Overriding )
注意上面提到的方法描述符,前面讲过方法描述符包含了参数类型及返回值,JVM要求这两个必须完全相同才可以,但是Java语言说的是参数类型相同但是返回值类型可以不同。Java编译器通过创建Bridge Method来解决这个问题,看下面代码:
class A { Object f() { return null; } } class C extends A { Integer f() { return null; } }
然后用 javap
查看编译结果:
$ javac Override.java $ javap -v C.class class C extends A ... { java.lang.Integer f(); descriptor: ()Ljava/lang/Integer; flags: Code: stack=1, locals=1, args_size=1 0: aconst_null 1: areturn ... java.lang.Object f(); descriptor: ()Ljava/lang/Object; flags: ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokevirtual #2 // Method f:()Ljava/lang/Integer; 4: areturn LineNumberTable: line 7: 0 }
可以看到编译器替我们创建了一个 Object f()
的Bridge Method,它调用的是 Integer f()
,这样就构成了JVM所定义的重写。