1.什么是桥接方法
桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。
判断方法
我们可以通过 Method.isBridge()
来判断一个方法是不是桥接方法。
桥接方法的 access_flag
在字节码中,桥接方法会被标记 ACC_BRIDGE 和 ACC_SYNTHETIC
ACC_BRIDGE
用来说明 桥接方法是由 Java 编译器 生成的ACC_SYNCTHETIC
用来表示 该类成员没有出现在源代码中,而是由编译器生成
2.什么情况下会产生桥接方法(探究)
情况1:实现一个泛型接口
Consumer<T>
是 JDK 1.8 出现的接口方法:
public interface Consumer<T> {
void accept(T t);
}
我们来实现该接口
public class StringConsumer implements Consumer<String> {
@Override
public void accept(String s) {
System.out.println("i consumed " + s);
}
}
编译源代码后,查看字节码。
- 使用 IDEA: View -> Show Bytecode
我们发现:
StringConsumer 类的字节码中包含 桥接方法
StringConsumer 类的字节码中还包含我们覆写后的 public void accept(String s)
方法
类似地,还有 JDK 1.8 出现的 Supplier<T>
和 Function<T,R>
, 接口实现类
public class StringSupplier implements Supplier<String> {
@Override
public String get() {
return "null";
}
}
桥接方法如图所示:
相当于字节码中有一个源代码中所不存在的,由编译器产生的public Object get()
方法。
public class StringFunction implements Function<String, String> {
@Override
public String apply(String s) {
return s + " , hello";
}
}
桥接方法如图所示:
相当于字节码中有一个源代码中所不存在的,由编译器产生的public Object apply(Object p)
方法。
情况2:覆盖超类的方法,并升级返回类型
- 修改参数类型(×)
修改参数数量,类型,参数顺序,都会产生一个新的方法。
// 超类
public class BaseConsumer {
public void accept(Object object) {}
}
// 子类
public class IntegerConsumer extends BaseConsumer {
public void accept(Object integer) {
System.out.println("consumed " + integer);
}
}
- 修改返回类型(√)
查看IntegerSupplier
字节码可以找到桥接方法public Object get()
// 超类
public class BaseSupplier {
public Object get() {
return "base";
}
}
// 子类
public class IntegerSupplier extends BaseSupplier {
public Integer get() {
return 110;
}
}
- 保持原样(×)
我们知道修改了参数不会产生桥接方法,但是我们保持方法的返回类型,方法名,参数,它也还是不会产生桥接方法
// 超类
public class BaseFunction {
public Object apply(Object src) {
return src;
}
}
// 子类
public class IntegerFunction extends BaseFunction {
public Object apply(Object src) {
return src;
}
}
情况3:升级修饰符不会生成桥接方法
- 访问限定符升级(×)
// 超类
public class BasePrinter {
protected void print() {
}
}
// 子类
public class PublicPrinter extends BasePrinter {
public void print() {
System.out.println("print");
}
}
情况4:静态方法不会生成桥接方法
- 子类的静态方法属于子类,父类的静态方法属于父类。
- 静态方法在继承中不存在覆盖。也不会生成桥接方法
// 超类
public class SuperClass {
public static void staticMethod() {
System.out.println("SuperClass");
}
}
// 子类
public class SubClass extends SuperClass {
public static void staticMethod() {
System.out.println("SubClass");
}
}
3. 为什么要生成桥接方法
我们再来看这个方法
public class StringFunction implements Function<String, String> {
@Override
public String apply(String s) {
return s + " , hello";
}
public static void main(String[] args) {
Function func = new StringFunction();
System.out.println(func.apply(new Object()));
}
}
运行main函数,会产生以下异常。
发生错误的代码是func.apply(new Object())
,这里会调用桥接方法。
查看字节码,我们发现,桥接方法进行运行时类型检查时,会抛出异常,但是真正发生作用的还是 StringFunction 类 String apply(String s)
。
我们重新聚焦为什么要生成桥接方法
在java1.5以前,比如声明一个集合类型:
List list = new ArrayList();
那么往list中可以添加任何类型的对象,但是在从集合中获取对象时,无法确定获取到的对象是什么具体的类型,所以在1.5的时候引入了泛型,在声明集合的时候就指定集合中存放的是什么类型的对象:
List<String> list = new ArrayList<String>();
使用泛型之后,在获取时就不必担心类型的问题,因为泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型,如果不正确会在编译时就会发现错误,而不必等到运行时才发现错误。
因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),产生桥接方法。
总结
桥接方法的产生:
- 实现 泛型接口 的类,编译器会为 这个类生成 桥接方法
- 继承 超类的 方法,并且升级方法的返回类型(即子类 覆写 超类方法 时,返回类型 升级为 原返回类型的父类)
为什么需要生成桥接方法?
- 因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),产生桥接方法
参考文献
java中什么是bridge method(桥接方法)
桥接方法的 access_flag
Java Language Specification