写点什么

3 分钟快速搞懂 Java 的桥接方法,Java 多态实现原理解析

用户头像
Java高工P7
关注
发布于: 6 小时前

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_BRIDGEACC_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_BRIDGEACC_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 方法是如何实现的:

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
3分钟快速搞懂Java的桥接方法,Java多态实现原理解析