java中什么是bridge method(桥接方法)

来源:互联网 发布:idc销售网站源码 编辑:程序博客网 时间:2024/06/06 09:14

http://blog.csdn.net/mhmyqn/article/details/47342577

在看spring-mvc的源码的时候,看到在解析handler方法时,有关于获取桥接方法代码,不明白什么是桥接方法,经过查找资料,终于理解了什么是桥接方法。

什么是桥接方法

桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。

我们可以通过Method.isBridge()方法来判断一个方法是否是桥接方法,在字节码中桥接方法会被标记为ACC_BRIDGE和ACC_SYNTHETIC,其中ACC_BRIDGE用于说明这个方法是由编译生成的桥接方法,ACC_SYNTHETIC说明这个方法是由编译器生成,并且不会在源代码中出现。可以查看jvm规范中对这两个access_flag的解释http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6

有如下3个问题:

  • 什么时候会生成桥接方法
  • 为什么要生成桥接方法
  • 如何通过桥接方法获取实际的方法

什么时候会生成桥接方法

那什么时候编译器会生成桥接方法呢?可以查看JLS中的描述http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.4.5

就是说一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法(当然还有其他情况会生成桥接方法,这里只是列举了其中一种情况)。如下所示:

[java] view plain copy
  1. package com.mikan;  
  2.   
  3. /** 
  4.  * @author Mikan 
  5.  * @date 2015-08-05 16:22 
  6.  */  
  7. public interface SuperClass<T> {  
  8.   
  9.     T method(T param);  
  10.   
  11. }  
  12.   
  13. package com.mikan;  
  14.   
  15. /** 
  16.  * @author Mikan 
  17.  * @date 2015-08-05 17:05 
  18.  */  
  19. public class SubClass implements SuperClass<String> {  
  20.     public String method(String param) {  
  21.         return param;  
  22.     }  
  23. }  

来看一下SubClass的字节码:

[plain] view plain copy
  1. localhost:mikan mikan$ javap -c SubClass.class  
  2. Compiled from "SubClass.java"  
  3. public class com.mikan.SubClass implements com.mikan.SuperClass<java.lang.String> {  
  4.   public com.mikan.SubClass();  
  5.     flags: ACC_PUBLIC  
  6.     Code:  
  7.       stack=1, locals=1, args_size=1  
  8.          0: aload_0  
  9.          1: invokespecial #1                  // Method java/lang/Object."<init>":()V  
  10.          4: return  
  11.       LineNumberTable:  
  12.         line 7: 0  
  13.       LocalVariableTable:  
  14.         Start  Length  Slot  Name   Signature  
  15.                0       5     0  this   Lcom/mikan/SubClass;  
  16.   
  17.   public java.lang.String method(java.lang.String);  
  18.     flags: ACC_PUBLIC  
  19.     Code:  
  20.       stack=1, locals=2, args_size=2  
  21.          0: aload_1  
  22.          1: areturn  
  23.       LineNumberTable:  
  24.         line 11: 0  
  25.       LocalVariableTable:  
  26.         Start  Length  Slot  Name   Signature  
  27.                0       2     0  this   Lcom/mikan/SubClass;  
  28.                0       2     1 param   Ljava/lang/String;  
  29.   
  30.   public java.lang.Object method(java.lang.Object);  
  31.     flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC  
  32.     Code:  
  33.       stack=2, locals=2, args_size=2  
  34.          0: aload_0  
  35.          1: aload_1  
  36.          2: checkcast     #2                  // class java/lang/String  
  37.          5: invokevirtual #3                  // Method method:(Ljava/lang/String;)Ljava/lang/String;  
  38.          8: areturn  
  39.       LineNumberTable:  
  40.         line 7: 0  
  41.       LocalVariableTable:  
  42.         Start  Length  Slot  Name   Signature  
  43.                0       9     0  this   Lcom/mikan/SubClass;  
  44.                0       9     1    x0   Ljava/lang/Object;  
  45. }  
  46. localhost:mikan mikan$  
SubClass只声明了一个方法,而从字节码可以看到有三个方法,第一个是无参的构造方法(代码中虽然没有明确声明,但是编译器会自动生成),第二个是我们实现的接口中的方法,第三个就是编译器自动生成的桥接方法。可以看到flags包括了ACC_BRIDGE和ACC_SYNTHETIC,表示是编译器自动生成的方法,参数类型和返回值类型都是Object。再看这个方法的字节码,它把Object类型的参数强制转换成了String类型,再调用在SubClass类中声明的方法,转换过来其实就是:

[java] view plain copy
  1. public Object method(Object param) {  
  2.     return this.method(((String) param));  
  3. }  

也就是说,桥接方法实际是是调用了实际的泛型方法,来看看下面的测试代码:

[java] view plain copy
  1. package com.mikan;  
  2.   
  3. /** 
  4.  * @author Mikan 
  5.  * @date 2015-08-07 16:33 
  6.  */  
  7. public class BridgeMethodTest {  
  8.   
  9.     public static void main(String[] args) throws Exception {  
  10.         SuperClass superClass = new SubClass();  
  11.         System.out.println(superClass.method("abc123"));// 调用的是实际的方法  
  12.         System.out.println(superClass.method(new Object()));// 调用的是桥接方法  
  13.     }  
  14.   
  15. }  
这里声明了SuperClass类型的变量指向SubClass类型的实例,典型的多态。在声明SuperClass类型的变量时,不指定泛型类型,那么在方法调用时就可以传任何类型的参数,因为SuperClass中的方法参数实际上是Object类型,而且编译器也不能发现错误。在运行时当参数类型不是SubClass声明的类型时,会抛出类型转换异常,因为这时调用的是桥接方法,而在桥接方法中会进行强制类型转换,所以才会抛出类型转换异常。上面的代码输出结果如下:
[java] view plain copy
  1. abc123  
  2. Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String  
  3.     at com.mikan.SubClass.method(SubClass.java:7)  
  4.     at com.mikan.BridgeMethodTest.main(BridgeMethodTest.java:27)  
  5.     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
  6.     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)  
  7.     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  
  8.     at java.lang.reflect.Method.invoke(Method.java:606)  
  9.     at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)  
如果我们在声明SuperClass类型的变量就指定了泛型类型:
[java] view plain copy
  1. SuperClass<String> superClass = new SubClass();  
当然这里类型只能是String,因为SubClass的泛型类型声明是String类型的,如果指定其他类型,那么在编译时就会错误,这样就把类型检查从运行时提前到了编译时。这就是泛型的好处。

为什么要生成桥接方法

上面看到了编译器在什么时候会生成桥接方法,那为什么要生成桥接方法呢?

在java1.5以前,比如声明一个集合类型:

[java] view plain copy
  1. List list = new ArrayList();  
那么往list中可以添加任何类型的对象,但是在从集合中获取对象时,无法确定获取到的对象是什么具体的类型,所以在1.5的时候引入了泛型,在声明集合的时候就指定集合中存放的是什么类型的对象:
[java] view plain copy
  1. List<String> list = new ArrayList<String>();  
那么在获取时就不必担心类型的问题,因为泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型,如果不正确会在编译时就会发现错误,而不必等到运行时才发现错误。因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),但是我们还是可以通过反射API来获取泛型的信息,在编译时可以通过泛型来保证类型的正确性,而不必等到运行时才发现类型不正确。由于java泛型的擦除特性,如果不生成桥接方法,那么与1.5之前的字节码就不兼容了。如前面的SuperClass中的方法,实际在编译后的字节码如下:

[java] view plain copy
  1. localhost:mikan mikan$ javap -c -v SuperClass.class  
  2. Classfile /Users/mikan/Documents/workspace/project/algorithm/target/classes/com/mikan/SuperClass.class  
  3.   Last modified 2015-8-7; size 251 bytes  
  4.   MD5 checksum 2e2530041f1f83aaf416a2ca3af9b7e3  
  5.   Compiled from "SuperClass.java"  
  6. public interface com.mikan.SuperClass<T extends java.lang.Object>  
  7.   Signature: #7                           // <T:Ljava/lang/Object;>Ljava/lang/Object;  
  8.   SourceFile: "SuperClass.java"  
  9.   minor version: 0  
  10.   major version: 51  
  11.   flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT  
  12. Constant pool:  
  13.    #1 = Class              #10            //  com/mikan/SuperClass  
  14.    #2 = Class              #11            //  java/lang/Object  
  15.    #3 = Utf8               method  
  16.    #4 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;  
  17.    #5 = Utf8               Signature  
  18.    #6 = Utf8               (TT;)TT;  
  19.    #7 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;  
  20.    #8 = Utf8               SourceFile  
  21.    #9 = Utf8               SuperClass.java  
  22.   #10 = Utf8               com/mikan/SuperClass  
  23.   #11 = Utf8               java/lang/Object  
  24. {  
  25.   public abstract T method(T);  
  26.     flags: ACC_PUBLIC, ACC_ABSTRACT  
  27.     Signature: #6                           // (TT;)TT;  
  28. }  
  29. localhost:mikan mikan$  
通过Signature: #7   // <T:Ljava/lang/Object;>Ljava/lang/Object;可以看到,在编译完成后泛型实际上就成了Object了,所以方法实际上成了
[java] view plain copy
  1. public abstract Object method(Object param);  
而SubClass实现了SuperClass这个接口,如果不生成桥接方法,那么SubClass就没有实现接口中声明的方法,语义就不正确了,所以编译器才会自动生成桥接方法,来保证兼容性。

如何通过桥接方法获取实际的方法

我们在通过反射进行方法调用时,如果获取到桥接方法对应的实际的方法呢?可以查看spring中org.springframework.core.BridgeMethodResolver类的源码。实际上是通过判断方法名、参数的个数以及泛型类型参数来获取的。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 动车如果错过了怎么办 没有取票错过了怎么办 动车出站没检票怎么办 火车晚点耽误了下班车怎么办 动车票中途丢了怎么办 购买二手房异地铁路公积金怎么办 沈阳公积金卡丢了怎么办 住宅专项维修资金用完了怎么办 广州出租车丢了东西怎么办 广州的士丢了东西怎么办 网上找兼职被骗了怎么办 海信空调开不了机怎么办 海信空调遥控器开不了怎么办 学生遭套路贷反被仲裁怎么办 赏脸打错字尝脸怎么办 红掌的花变黑了怎么办 红掌花苞发黑了怎么办 水培植物腐根了怎么办 水培绿萝水发臭怎么办 水里养花根烂掉怎么办 桅子花叶子发黑怎么办 大株月季烂根怎么办 月季水浇多了烂根的怎么办 金桔盆栽烂根怎么办 盆栽的长寿果树烂根怎么办 家里的石榴烂根怎么办 山桔盆栽烂根怎么办 养的植物烂根怎么办 桅子花叶子长霉怎么办 紫薇花叶子干了怎么办 高层玻璃阳台往下看恐高怎么办 比熊放阳台叫怎么办 海员入职体检不合格怎么办 联币金融立案投资人怎么办 联币金融的投资怎么办 养老保险领了几个月就挂了怎么办 高铁餐吧乘务员东西卖不出去怎么办 铁路局如果查出有乙肝怎么办 在火车站丢了东西怎么办 在新乡火车站丢了东西怎么办 自己的行李忘到高铁安检怎么办