如果去翻近几年大厂的Java面试八股文,泛型这块有一个高频追问点,我几乎每次帮人模拟面试都会碰到:“泛型在运行时会被擦除,那多态是怎么保住的?”“为什么有时候通过反射能看到两个同名方法,其中一个是编译器生成的?”
这背后藏着的就是桥方法。
桥方法(Bridge Method)是javac编译器在泛型擦除之后,为了保证多态而自动合成的一个“中转方法”。它长得像普通方法,但不出现在你的源码里,只在字节码中存在。很多人学泛型只停留在“尖括号”“类型擦除”“T会被擦成Object”这种表面理解,一问到桥方法就卡壳。这篇文章我会把桥方法从产生原理、字节码长相、面试答题话术到实战影响全部拆一遍,读完你再去面试,这一块基本不会丢分。
1. 先搞清楚:桥方法到底是解决什么问题的
1.1 泛型擦除带来的麻烦
Java泛型是编译期概念,运行时的JVM根本不知道T是什么。编译器在生成字节码之前,会把泛型信息抹掉,把类型变量替换成它的上界(通常是Object)。这个过程叫类型擦除。
擦除本身不是问题,问题在于:擦除之后,方法签名可能会跟多态要求冲突。
举个例子。定义一个泛型接口:
java复制public interface Generator<T> {
T generate();
}
擦除之后,javac眼里这个接口长这样:
java复制public interface Generator {
Object generate();
}
注意,接口里方法的返回值从T变成了Object,因为你没给T指定上界,上界就是Object。
现在写一个实现类,让它生成Integer:
java复制public class IntegerGenerator implements Generator<Integer> {
@Override
public Integer generate() {
return 100;
}
}
源码看起来没问题,子类和接口的泛型版本对得上。但擦除之后呢?你实现的其实是:
java复制public class IntegerGenerator implements Generator {
@Override
public Integer generate() {
return 100;
}
}
接口要求的是 Object generate(),你的类是 Integer generate()。在JVM的视角里,这两个方法的描述符根本不一样——返回值类型不同,方法签名就不同。这在编译器的类型检查里能通过(因为Integer可以向上转型为Object),但在运行时的方法分派里,JVM按方法名加参数类型找方法,这里参数都是空的,方法名也一样,返回值类型不参与签名匹配,所以IntegerGenerator其实并没有真正实现接口的Object generate()。
多态马上就断了。
1.2 多态与擦除碰撞出的“隐蔽缺口”
如果你把IntegerGenerator看作接口的实现者,调用方拿着Generator引用:
java复制Generator<Integer> gen = new IntegerGenerator();
Object value = gen.generate();
这段代码能跑吗?理想情况它应该调用IntegerGenerator的generate()。但如果编译器就这么不管,运行时JVM会发现Generator接口的方法描述符是()Object,而IntegerGenerator里只有()Integer,匹配不上,最终就会抛出AbstractMethodError。
这个场景有个术语叫“类型擦除后的多态冲突”。而桥方法就是编译器为了填补这个缺口,自动生成的“缝合”方法。
javac会偷偷在IntegerGenerator里多加一个方法:
java复制public Object generate() {
return generate(); // 调用Integer版本
}
注意这个Object签名版本的方法,就是桥方法。它的逻辑很简
