桥接方法是编译器为解决泛型擦除后签名不匹配而生成的“翻译层”,其签名与父类擦除后方法完全一致,仅做类型转换并转发调用,被标记为ACC_BRIDGE和ACC_SYNTHETIC,确保多态在运行时正确分派。

Java 中的方法重写在字节码层面并不总是像源码中看起来那样“直接”。泛型擦除后,子类方法签名与父类(或接口)擦除后的签名可能不一致,JVM 就无法识别为重写——这时编译器会悄悄补上一个桥接方法,靠 ACC_BRIDGE 和 ACC_SYNTHETIC 这两个标志来标记它。
桥接方法出现的核心原因:签名对齐
泛型只存在于编译期,运行时所有类型参数都被擦除。比如接口定义了 <t> T get()</t>,擦除后变成 Object get();而你实现时写了 String get(),虽然语义上是重写,但 JVM 看到的是两个不同签名:()Ljava/lang/Object; 和 ()Ljava/lang/String;。这会被当作重载而非重写,多态就失效了。
- 桥接方法就是编译器生成的“翻译层”,签名完全匹配擦除后的父类/接口方法
- 它的作用只是做一次强制转型,再调用你写的那个真实方法
- 没有它,
invokevirtual指令在运行时就会找不到可分派的重写版本
如何从字节码中识别桥接方法
用 javap -v ClassName.class 查看完整字节码,重点观察每个方法块末尾的 flags: 行:
- 同时包含
ACC_BRIDGE和ACC_SYNTHETIC的,就是桥接方法 - 它通常没有
LineNumberTable(无源码行号),LocalVariableTable为空 - 方法体极短,一般就两行:
aload_0+invokevirtual调用真实方法 +areturn或areturn前加类型转换
例如:
public java.lang.Object get();<br> descriptor: ()Ljava/lang/Object;<br> flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC<br> Code:<br> 0: aload_0<br> 1: invokevirtual #23 // Method get:()Ljava/lang/String;<br> 4: areturn
桥接方法不是 bug,而是兼容性设计
它不是编译错误,也不是你代码写错了,而是 Java 为兼顾泛型语法和 JVM 字节码向后兼容所必须的补偿机制:
- 目标是让 JDK 5 引入泛型后,新代码仍能被旧 JVM 正确加载和执行
- 它让泛型类继承、泛型接口实现、协变返回值等场景下的多态行为保持一致
- 反射中也能看到它:
Method.isBridge()返回true,getDeclaredMethods()会包含它
哪些情况一定会触发桥接方法生成
不是所有泛型使用都会产生桥接方法,只有满足以下全部条件时才会出现:
- 父类型(类或接口)声明了带类型参数的方法,如
<t> void accept(T t)</t> - 子类型将该方法具体化,比如实现为
void accept(String s) - 擦除后,父类方法签名与子类方法签名不一致(如
ObjectvsString) - 且该子类方法带有
@Override(显式或隐式),表明意图重写
常见典型场景包括:实现 Comparable<string></string>、继承 ArrayList<string></string> 并重写 add、泛型类继承链中的 get() 方法等。











