3 分钟快速搞懂 Java 的桥接方法,Java 多态实现原理解析
javac Child.java
javap -v -c Child.class
结果如下:
public class Child extends Parent
......省略部分结果......
java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
flags:
Code:
stack=1, locals=1, args_size=1
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 5: 0
java.lang.Number get();
descriptor: ()Ljava/lang/Number;
flags: ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #3 // Method get:()Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 1: 0
从上面的结果可以看到,有一个方法 java.lang.Number get(), 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGE
和ACC_SYNTHETIC
,就是我们前面所说的桥接方法。
这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual
指令再调用方法 java.lang.Integer get()。
**编译器这么做的原因是什么呢?**因为在 JVM 方法中,返回类型也是方法签名的一部分,而桥接方法的签名和其父类的方法签名一致,以此就实现了协变返回值类型。
类型擦除
泛型是 Java 1.5 才引进的概念,在这之前是没有泛型的概念的,但泛型代码能够很好地和之前版本的代码很好地兼容,这是为什么呢?
这是因为,在编译期间 Java 编译器会将类型参数替换为其上界(类型参数中 extends 子句的类型),如果上界没有定义,则默认为 Object,这就叫做类型擦除。
当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法,例如:
public class Parent<T> {
void set(T t) {
}
}
public class Child extends Parent<String> {
@Override
void set(String str) {
}
}
Child 类在继承其父类 Parent 的泛型方法时,明确指定了泛型类型为 String,将这段代码进行编译,再反编译:
public class Child extends Parent<java.lang.String>
......省略部分结果......
void set(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags:
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 5: 0
void set(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #2 // class java/lang/String
5: invokevirtual #3 // Method set:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
从上面的结果可以看到,有一个方法 void set(java.lang.Object), 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGE
和ACC_SYNTHETIC
,就是我们前面所说的桥接方法。
这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual
指令再调用方法 void set(java.lang.String)。
**编译器这么做的原因是什么呢?**因为 Parent 类在类型擦除之后,变成这样:
public class Parent<Object> {
void set(Object t) {
}
}
编译器为了让子类有一个与父类的方法签名一致的方法,就在子类自动生成一个与父类的方法签名一致的桥接方法。
如何获取桥接方法的实际方法
在 Spring Framework 中已经实现了获取桥接方法的实际方法的功能,就在 spring-core 模块中的 BridgeMethodResolver 类中,像这样直接使用就行了:
method = BridgeMethodResolver.findBridgedMethod(method);
findBridgedMethod 方法是怎么实现的呢?我们来分析一下源码(spring-core 的版本为 5.2.8.RELEASE):
public static Method findBridgedMethod(Method bridgeMethod) {
// 如果不是桥连方法,就直接返回原方法。
if (!bridgeMethod.isBridge()) {
return bridgeMethod;
}
// 先从本地缓存读取,缓存中有则直接返回。
Method bridgedMethod = cache.get(bridgeMethod);
if (bridgedMethod == null) {
List<Method> candidateMethods = new ArrayList<>();
// 以方法名称和入参个数相等为筛选条件。
MethodFilter filter = candidateMethod ->
isBridgedCandidateFor(candidateMethod, bridgeMethod);
// 递归该类及其所有父类上的所有方法,符合筛选条件就添加进来。
ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass()
, candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) {
// 如果符合筛选条件的方法个数为 1,则直接采用;
// 否则,调用 searchCandidates 方法再次筛选。
bridgedMethod = candidateMethods.size() == 1 ?
candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod);
}
// 如果找不到实际方法,则返回原来的桥连方法。
if (bridgedMethod == null) {
// A bridge method was passed in but we couldn't find the bridged method.
// Let's proceed with the passed-in method and hope for the best...
bridgedMethod = bridgeMethod;
}
// 把查找的结果放入内存缓存。
cache.put(bridgeMethod, bridgedMethod);
}
return bridgedMethod;
}
我们再看一下再次筛选的 searchCandidates 方法是如何实现的:
评论