目录
桥接方法概念 什么情况下会生成桥接方法 协变返回类型 泛型类型擦除 查询桥接方法
Java 桥接方法
桥接方法概念
Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法。可以通过使用Java反射中 Method 类的 isBridge() 方法来判断该方法是否是桥接方法。通过反射 Class.getMethod("") 取出的不是桥接方法。
在字节码文件中,桥接方法会被标记为 ACC_BRIDGE 和 ACC_SYNTHETIC,其中 ACC_BRIDGE 表示该方法是由编译器产生的桥接方法, ACC_SYNTHETIC 表示该方法是由编译器自动生成。
什么情况下会生成桥接方法
协变返回类型
协变返回类型是指子类方法的返回值类型不必严格等同于父类中被重写的方法的返回值类型,而可以是更具体的类型,即子类重写父类方法时,返回的类型可以是子类方法返回类型的子类。
看如下代码:
public class Parent { public Number get(){ return 0; } } class Child1 extends Parent { public Number get(){ return 1; } } class Child2 extends Parent { public Integer get(){ return 2; } }
运行以下命令,编译并查看字节码数据:
javac Parent.java javap -c -v Child1 javap -c -v Child2
显示结果如下(省略不重要的部分代码):
{ Child1(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method Parent."<init>":()V 4: return LineNumberTable: line 9: 0 public java.lang.Number get(); descriptor: ()Ljava/lang/Number; flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: iconst_1 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: areturn LineNumberTable: line 12: 0 }
{ Child2(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method Parent."<init>":()V 4: return LineNumberTable: line 16: 0 public java.lang.Integer get(); descriptor: ()Ljava/lang/Integer; flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: iconst_2 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: areturn LineNumberTable: line 19: 0 public java.lang.Number get(); descriptor: ()Ljava/lang/Number; flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokevirtual #3 // Method get:()Ljava/lang/Integer; 4: areturn LineNumberTable: line 16: 0 }
对比一下 Child1 和 Child2 的字节码可以看出,Child2 中多了一个 public java.lang.Number get(); 方法, 这个方法就是桥接方法,可以通过该方法的 flags 看到它含有 ACC_BRIDGE 标志。这个方法就是一个桥接作用,从第30行可以看出它的方法内容,就是通过 invokevirtual 调用了自身的 get() 方法。为什么需要通过这种方式来调用呢,是因为在JVM来说一个方法的签名比Java语言多了一个返回值类型,也就是说,在Java语言中,认为只要方法名和参数列表一致就是同一个方法,而JVM则认为方法名、参数列表和返回类型全部一样才是同一方法。而桥接方法的签名和其父类的方法签名一致,以此就实现了协变返回值类型。
泛型类型擦除
泛型是Java 1.5才引进的概念,在这之前是没有泛型的概念的,但泛型代码能够很好地和之前版本的代码很好地兼容,这是因为,在编译期间Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,这就叫做类型擦除。当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法。
看如下代码:
public class Parent<T> { public Number get(T key){ return 0; } } class Child1 extends Parent<String> { public Number get(String key){ return 1; } } class Child2 extends Parent<String> { public Integer get(String key){ return 2; } }
运行以下命令,编译并查看字节码数据:
javac Parent.java javap -c -v Child1 javap -c -v Child2
显示结果如下(省略不重要的部分代码):
{ Child1(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method Parent."<init>":()V 4: return LineNumberTable: line 9: 0 public java.lang.Number get(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/Number; flags: ACC_PUBLIC Code: stack=1, locals=2, args_size=2 0: iconst_1 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: areturn LineNumberTable: line 12: 0 public java.lang.Number get(java.lang.Object); descriptor: (Ljava/lang/Object;)Ljava/lang/Number; flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: checkcast #3 // class java/lang/String 5: invokevirtual #4 // Method get:(Ljava/lang/String;)Ljava/lang/Number; 8: areturn LineNumberTable: line 9: 0 }
{ Child2(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method Parent."<init>":()V 4: return LineNumberTable: line 16: 0 public java.lang.Integer get(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/Integer; flags: ACC_PUBLIC Code: stack=1, locals=2, args_size=2 0: iconst_2 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: areturn LineNumberTable: line 19: 0 public java.lang.Number get(java.lang.Object); descriptor: (Ljava/lang/Object;)Ljava/lang/Number; flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: checkcast #3 // class java/lang/String 5: invokevirtual #4 // Method get:(Ljava/lang/String;)Ljava/lang/Integer; 8: areturn LineNumberTable: line 16: 0 }
对比一下 Child1 和 Child2 的字节码可以看出,Child2 中多了一个 public java.lang.Number get(java.lang.Object); 方法, 这个方法就是桥接方法,可以通过该方法的 flags 看到它含有 ACC_BRIDGE 标志。还可以看出这个桥接方法的参数类型是 Object 类型,它与父类的参数类型是一致的,包括返回值也是与父类的类型是一致的。它的方法内容就是先进行类型检查(第31行),然后再通过 invokevirtual 指令调用自身的 get(java.lang.String); 方法。
对于JVM来说,当它编译时,它会直接把类型进行擦除,也就是会把类 Parent 变成如下形式:
public class Parent<Object> { public Number get(Object key){ return 0; } }
查询桥接方法
使用 spring 提供的查找方式:
method = BridgeMethodResolver.findBridgedMethod(method);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2016-06-15 UVa 1596 Bug Hunt (STL栈)