桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。可以通过Method.isBridge()方法来判断一个方法是否是桥接方法,在字节码中桥接方法会被标记为ACC_BRIDGE和ACC_SYNTHETIC
从jvm规范中截取的两条记录,可以看到,ACC_BRIDGE表示方法是一个bridge method,而ACC_SYNTHETIC则表示该方法不在源码中展示。
Flag Name | Value | Interpretation |
---|---|---|
ACC_BRIDGE | 0x0040 | A bridge method, generated by the compiler. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
什么是桥接方法呢
When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, which is called a bridge method, as part of the type erasure process. You normally don’t need to worry about bridge methods, but you might be puzzled if one appears in a stack trace.
简单翻译一下就是:当编译一个类(或接口)继承了一个泛型类(或实现了一个泛型接口),编译器可能需要创建一个合成方法(其实就是类型转换用的方法),用于泛型的类型安全处理。使用者不需要关心这个方法,但是从堆栈里看到这个方法可能会让人疑惑。
通过官网的例子具体看下什么是桥接方法
/**
* 带泛型参数的父类
*/
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
/**
* 使用泛型的子类
*/
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
因为Java的泛型会进行类型擦除,所以编译后会变成这样
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
可以看到Node.setData(T)变成了Node.setData(Object),缺失了对父类方法的重写,所以就变成了下面这样
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
// Bridge method generated by the compiler
//
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
通过javap看下编译后的内容,可以看到MyNode类里多了一个public void setData(java.lang.Object)方法。
$ javap MyNode.class
Compiled from "MyNode.java"
public class com.st.bytecode.MyNode extends com.st.bytecode.Node<java.lang.Integer> {
public com.st.bytecode.MyNode(java.lang.Integer);
public void setData(java.lang.Integer);
public void setData(java.lang.Object);
}
$ javap -c MyNode.class
Compiled from "MyNode.java"
public class com.st.bytecode.MyNode extends com.st.bytecode.Node<java.lang.Integer> {
public com.st.bytecode.MyNode(java.lang.Integer);
Code:
0: aload_0
1: aload_1
2: invokespecial #1 // Method com/st/bytecode/Node."<init>":(Ljava/lang/Object;)V
5: return
public void setData(java.lang.Integer);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String MyNode.setData
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: aload_1
10: invokespecial #5 // Method com/st/bytecode/Node.setData:(Ljava/lang/Object;)V
13: return
public void setData(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #6 // class java/lang/Integer
5: invokevirtual #7 // Method setData:(Ljava/lang/Integer;)V
8: return
}
为什么要生成bridge方法呢
简单来说, 编译器生成bridge method的目的就是为了和jdk1.5之前的字节码兼容。因为范型是在jdk1.5之后才引入的。在jdk1.5之前例如集合的操作都是没有范型支持的, 所以生成的字节码中参数都是用Object接收的, 所以也可以往集合中放入任意类型的对象, 集合类型的校验也被拖到运行期。
但是在jdk1.5之后引入了范型, 因此集合的内容校验被提前到了编译期, 但是为了兼容jdk1.5之前的版本java使用了范型擦除, 所以如果不生成桥接方法就和jdk1.5之前的字节码不兼容了。