类型擦除
我们知道,Java 中的泛型在编译期间会将泛型信息擦除。如代码定义 List 和 List,编译之后都会变成 List。我们再考虑一种常见的情形:Java 类库中比较器的用法。我们自定义比较器的时候,可以通过实现 Comparator 接口,实现比较逻辑。示例代码如下:
public class MyComparator implements Comparator<Integer> { public int compare(Integer a,Integer b) { // 比较逻辑 } }
这种情况下,编译器同样会产生一个桥接方法。方法签名为 intcompare(Object a, Object b) 。
图 MyComparator 类的两个 compare 方法
伪代码示意,大概是这样:
public class MyComparator implements Comparator<Integer> { public int compare(Integer a,Integer b) { // 比较逻辑 } // 桥接方法 (bridge method) public int compare(Object a,Object b) { return compare((Integer)a,(Integer)b); } }
因此,当我们使用如下方式进行比较的时候,能够通过编译并得到我们预期的结果:
Object a = 5; Object b = 6; Comparator rawComp = new MyComparator(); // 可以通过编译,因为自动生成了桥接方法compare(Object a, Object b) int comp = rawComp.compare(a, b);
另外,我们知道,泛型编译之后,类型信息会被擦除。如果我们有这样一个比较方法:
// 比较方法
public <T> T max(List<T> list, Comparator<T> comparator){ T biggestSoFar = list.get(0); for ( T t : list ) { if (comparator.compare(t,biggestSoFar) > 0) { biggestSoFar = t; } } return biggestSoFar; }
编译之后,泛型被擦除掉,伪代码表示,大概是这样:
public Object max(List list, Comparator comparator) { Object biggestSoFar =list.get(0); for ( Object t : list ) { if (comparator.compare(t,biggestSoFar) > 0) { //比较逻辑
biggestSoFar = t; } } return biggestSoFar; }
我们将 MyComparator 其中一个参数传入 max() 方法。如果没有桥接方法的话,那么第四行的比较逻辑,将无法正确编译,因为MyComparator 类中没有两个参数是 Object 类型的比较方法,只有参数类型是 Integer 类型的比较方法。读者可自行测试。 解决方案
通过以上的案例描述,我们知道,在实现泛型接口的场景下,编译器会自动生成桥接方法,保证编译能够通过。那么在这种情况下,我们只要识别哪一个是桥接方法,哪一个不是桥接方法,就可以解决我们一开始的问题。很自然的,既然编译器自动产生了一个桥接方法,那么应该会有某种方式,可以让我们判断一个方法是否是桥接方法。
果然,我们继续研究发现,Method 类中提供了 Method#isBridge() 方法。查看源码中对方法的描述:Method#isBridge():Returns true if this method is a bridge method;returns false otherwise。
到此,我们通过反射,获取到 UserInfoOperator 类中的两个process 方法,再调用 Method#isBridge() 方法,即可锁定需要的方法,因而进一步获取方法参数 java.lang.String。 深入分析
至此可以说,就业务需求来说,我们完美的找到了解决方案。但在此之后,不禁会想:除了上述示例,还有哪些情况下,编译器也会自动生成桥接方法呢?我们继续深入研究。 类继承
通过查阅相关资料,我们考虑如下一种情况:
/** * 如下会产生桥接方法吗? * @author renzhiqiang * @date 2022/2/20 18:33 */ public class BridgeMethodSample { static class A { public void foo() { } } public static class C extends A{ } public static class D extends A{ @Override
public void foo() { } } }
上述代码示例中,我们定义了三个静态内部类:A C D,其中 C D 分别继承 A。经过编译,通过jclasslib 查看 BridgeMethodSample 字节码,我们也发现:类 C 中编译器为其生成了桥接方法 void foo(),而类 D 中却没有。
图 类C 生成桥接方法
图 类D 没有生成桥接方法
深入分析,并根据上述分析的经验,我们猜测,编译器生成桥接方法,一定是在某种情况下需要一个方法,来满足 Java 编程规范,或者需要保证程序运行的正确性。通过字节码可以看出,类 A 没有 public 修饰,包范围以外的程序是没有访问类 A 的权限的,更不用说类 A 中的方法。
但是类 C 是有public 修饰,C 类中的方法,包括继承来的方法,是可以被包外的程序访问的。因此,编译器需要生成一个桥接方法,以保证能够访问 foo() 方法,满足程序的正确运行。但是,类 D 同样继承 A,却没有生成桥接方法,根本原因是类 D 中重写了父类 A 中的 foo() 方法,即没有必要生成桥接方法。 方法重写
我们再看一种情况,方法重写。
Java 中,方法重写(Override),是子类对父类的允许访问的方法的实现过程进行重新编写的过程。重写需要满足一定的规则:
1. The method must have the same name as in the parentclass.
2. The method must have the same parameter as in theparent class.
3. There must be an IS-A relationship (inheritance).
JDK 5 之后,重写方法的返回类型,可以与父类方法返回类型相同,也可以不相同,但必须是父类方法返回类型的子类。我们考虑如下代码示例:
// 定义一个父类,包含一个 test() 方法
public class Father { public Object test(String s) { return s; } } // 定义一个子类,继承父类
public class Child extends Father { @Override
public String test(String s) { return s; } }
以上,在 Child 子类中,我们重写了 test() 方法,但是返回值的类型,我们将 java.lang.Object 改变为它的子类 java.lang.String。编译之后,我们同样使用 jclasslib 插件,查看两个类的字节码,如下所示:
图 Child 类字节码test() 方法(1)
图 Child 类字节码test() 方法(2)
图 Father类字节码test() 方法
根据上图我们发现,Child 类中我们重写了 test() 方法,但是在字节码层面,发现有两个 test() 方法,其中一个方法的访问标志为 [public synthetic bridge], 表示这个方法是编译器为我们生成的。而当我们不改变 Child#test() 方法的返回类型时,编译器并没有为我们生成桥接方法,读者可自行试验。
也就是说,在子类方法重写父类方法,返回类型不一致的情况下,编译器也为我们生成了桥接方法。
以上,笔者罗列了几种编译器为我们自动生成桥接方法的情况。那么是否还有其他场景下,编译器也会生成桥接方法呢?如果您也曾研究过或者使用过 bridge 方法,欢迎交流讨论。
同时,给出一个 bridge 方法的非官方定义,希望能够给读者一些启发:
Bridge Method: These are methods that create an intermediate layerbetween the source and the target functions. It is usually used as part of thetype erasure process. It means that the bridge method is required as a typesafe interface.
限于笔者水平有限,难免有理解不准确、不到位的地方。欢迎交流讨论! 参考
https://stackoverflow.com/questions/5007357/java-generics-bridge-method
https://stackoverflow.com/questions/14144888/find-generic-method-with-actual-types-from-getdeclaredmethods
https://www.geeksforgeeks.org/method-class-isbridge-method-in-java/
原文地址:https://mp.weixin.qq.com/s/iqr8_PckhYKrKREE53ovBA