bridge method又叫synthetic method,它是由Java编译器自动生成的一个合成方法,这个方法不会出现在源码中,也不能显式调用。我们先通过一个例子对bridge method有一个感性的认识。
// Code 1-1
class Animal {
public Animal getAnimal() {
return new Animal();
}
}
class Dog extends Animal {
public Dog getAnimal() {
return new Dog();
}
}
上面定义了Animal和Dog两个类,Dog是Animal的subclass,且Dog类override了Animal的getAnimal
方法。如果你现在尝试去编译Dog,很大概率是可以直接通过编译,不过当我们尝试使用JDK 1.4以下版本编译,就是另外一回事了。
javac org/wiyi/bridge/Dog.java
org/wiyi/bridge/Dog.java:6: getAnimal() in org.wiyi.bridge.Dog cannot override g
etAnimal() in org.wiyi.bridge.Animal; attempting to use incompatible return type
found : org.wiyi.bridge.Dog
required: org.wiyi.bridge.Animal
public Dog getAnimal() {
^
1 error
低版本的JDK编译会报错,提示的错误为”cannot override getAnimal() in org.wiyi.bridge.Animal;”。为什么无法override getAnimal方法呢?要了解这个,我们需要知道在JVM中对method override的定义。
An instance method
m1
declared in class C overrides another instance methodm2
declared in class A iff eitherm1
is the same asm2
, or all of the following are true:
- C is a subclass of A. //C是A的subclass
m1
has the same name and descriptor asm2
. // m1和m2的方法名和descriptor相同m1
is not markedACC_PRIVATE
. //m1不能为private- One of the following is true:
m2
is markedACC_PUBLIC
; or is markedACC_PROTECTED
; or is marked neitherACC_PUBLIC
norACC_PROTECTED
norACC_PRIVATE
and A belongs to the same run-time package as C.m1
overrides a methodm'
(m'
distinct fromm1
andm2
) such thatm'
overridesm2
.
根据上面的定义,我们可以逐个对比Dog类中到底哪条不满足,使用高版本的JDK编译Animal和Dog并查看对比它们的字节码。
#openjdk version "11.0.6
javap -v org.wiyi.bridge.Dog
javap -v org.wiyi.bridge.Animal
//Dog.class
org.wiyi.java9.generic.Dog getAnimal(); //1.方法名都是getAnimal
descriptor: ()Lorg/wiyi/java9/generic/Dog; //2.Dog的descriptor为Lorg/wiyi/java9/generic/Dog
flags: (0x0000)
Code:
stack=2, locals=1, args_size=1
0: new #2 // class org/wiyi/java9/generic/Dog
3: dup
4: invokespecial #3 // Method "<init>":()V
7: areturn
Start Length Slot Name Signature
0 8 0 this Lorg/wiyi/java9/generic/Dog;
//Animal.class
org.wiyi.java9.generic.Animal getAnimal(); //1.方法名为Animal
descriptor: ()Lorg/wiyi/java9/generic/Animal; //2.Animal的descriptor为Lorg/wiyi/java9/generic/Animal;
flags: (0x0000)
Code:
stack=2, locals=1, args_size=1
0: new #2 // class org/wiyi/java9/generic/Animal
3: dup
4: invokespecial #3 // Method "<init>":()V
7: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this Lorg/wiyi/java9/generic/Animal;
从字节码可以看出,Dog的getAnimal()
方法和Animal的getAnimal
方法的descriptor不同。因此,在JVM的层面上,它们是完全没有任何关系的两个方法,不能构成Override;因为方法重名参数一致,同时也不构成Overload,因此编译器会报错。
那么为什么高版本的Java编译器不会报错呢?因为JDK 1.5以上的版本支持了方法返回值的协变,编译源码时会生成一个Bridge Method实现Override。我们查看Dog类完整的字节码,会发现有两个getAnimal方法。
Classfile Dog.class
Compiled from "Dog.java"
public class org.wiyi.java9.generic.Dog extends org.wiyi.java9.generic.Animal
minor version: 0
major version: 55
{
public org.wiyi.java9.generic.Dog();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method org/wiyi/java9/generic/Animal."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/wiyi/java9/generic/Dog;
org.wiyi.java9.generic.Dog getAnimal(); //bigbyto注: 第一个getAnimal
descriptor: ()Lorg/wiyi/java9/generic/Dog;
flags: (0x0000)
Code:
stack=2, locals=1, args_size=1
0: new #2 // class org/wiyi/java9/generic/Dog
3: dup
4: invokespecial #3 // Method "<init>":()V
7: areturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this Lorg/wiyi/java9/generic/Dog;
org.wiyi.java9.generic.Animal getAnimal();//bigbyto注: 第二个getAnimal
descriptor: ()Lorg/wiyi/java9/generic/Animal;
flags: (0x1040) ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #4 // Method getAnimal:()Lorg/wiyi/java9/generic/Dog;
4: areturn
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/wiyi/java9/generic/Dog;
}
SourceFile: "Dog.java"
我们留意第二个getAnimal
方法,它的descriptor依然是Lorg/wiyi/java9/generic/Animal
,且它的flags多了两个ACC_BRIDGE, ACC_SYNTHETIC,这两个flag的意思即表明这是由编译器生成的Bridge Method。
第二个getAnimal
内部使用了invokevirtual这条指令,这意味着这个Bridge Method内部主要的逻辑就是直接调用第一个getAnimal
方法返回结果,它的伪代码如下:
class Dog extends Animal {
Dog getAnimal() {
return new Dog();
}
synthetic bridge Animal getAnimal() {
return ((Dog)this).getAnimal();
}
}
从上面的结果可以看出,Java语言和JVM它们对类型系统的定义其实存在一些差异。在本文的例子中,Dog的getAnimal方法把原返回值Animal改为了Dog,这属于方法返回值协变,是多态中的一部分。但在JVM中它们对它们的定义并不相同,因此通过Bridge Method的方式把两套类型系统连接到一起。
当我们理解了Bridge Method的本质,在看Oracle官网的Effects of Type Erasure and Bridge Methods就可以理解它为什么对于泛型会有很大的帮助。
参考资料:
JVM Bridge Methods with Dan Heidinga
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.5