Java中的bridge method(桥接方法)

java4all原创,欢迎关注
javascript

摘要:Java中的bridge method(桥接方法)是什么?php

1.什么是桥接方法css

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

咱们能够经过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。java

有以下3个问题:spring

  • 何时会生成桥接方法typescript

  • 为何要生成桥接方法oracle

  • 如何经过桥接方法获取实际的方法app

2.何时会生成桥接方法jvm

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

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

package com.mikan;


/**
 * @author Mikan
 * @date 2015-08-05 16:22
 */
public interface SuperClass<T> {
    T method(T param);
}


package com.mikan;
/**
 * @author Mikan
 * @date 2015-08-05 17:05
 */
public class SubClass implements SuperClass<String> {
    public String method(String param) {
        return param;
    }
}

来看一下SubClass的字节码:

localhost:mikan mikan$ javap -c SubClass.class
Compiled from "SubClass.java"
public class com.mikan.SubClass implements com.mikan.SuperClass<java.lang.String> {
  public com.mikan.SubClass();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Lcom/mikan/SubClass;


  public java.lang.String method(java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: areturn
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       2     0  this   Lcom/mikan/SubClass;
               0       2     1 param   Ljava/lang/String;


  public java.lang.Object method(java.lang.Object);
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #2                  // class java/lang/String
         5: invokevirtual #3                  // Method method:(Ljava/lang/String;)Ljava/lang/String;
         8: areturn
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       9     0  this   Lcom/mikan/SubClass;
               0       9     1    x0   Ljava/lang/Object;
}
localhost:mikan mikan$

SubClass只声明了一个方法,而从字节码能够看到有三个方法,第一个是无参的构造方法(代码中虽然没有明确声明,可是编译器会自动生成),第二个是咱们实现的接口中的方法,第三个就是编译器自动生成的桥接方法。能够看到flags包括了ACC_BRIDGE和ACC_SYNTHETIC,表示是编译器自动生成的方法,参数类型和返回值类型都是Object。再看这个方法的字节码,它把Object类型的参数强制转换成了String类型,再调用在SubClass类中声明的方法,转换过来其实就是:

public Object method(Object param) {
        return this.method(((String) param));
    }

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

package com.mikan;


/**
 * @author Mikan
 * @date 2015-08-07 16:33
 */
public class BridgeMethodTest {


    public static void main(String[] args) throws Exception {
        SuperClass superClass = new SubClass();
        System.out.println(superClass.method("abc123"));// 调用的是实际的方法
        System.out.println(superClass.method(new Object()));// 调用的是桥接方法
    }


}

这里声明了SuperClass类型的变量指向SubClass类型的实例,典型的多态。在声明SuperClass类型的变量时,不指定泛型类型,那么在方法调用时就能够传任何类型的参数,由于SuperClass中的方法参数其实是Object类型,并且编译器也不能发现错误。在运行时当参数类型不是SubClass声明的类型时,会抛出类型转换异常,由于这时调用的是桥接方法,而在桥接方法中会进行强制类型转换,因此才会抛出类型转换异常。上面的代码输出结果以下:

abc123
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
  at com.mikan.SubClass.method(SubClass.java:7)
  at com.mikan.BridgeMethodTest.main(BridgeMethodTest.java:27)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:606)
  at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

若是咱们在声明SuperClass类型的变量就指定了泛型类型:

SuperClass<String> superClass = new SubClass();

固然这里类型只能是String,由于SubClass的泛型类型声明是String类型的,若是指定其余类型,那么在编译时就会错误,这样就把类型检查从运行时提早到了编译时。这就是泛型的好处。

3.为何要生成桥接方法

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

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

List list = new ArrayList();

那么往list中能够添加任何类型的对象,可是在从集合中获取对象时,没法肯定获取到的对象是什么具体的类型,因此在1.5的时候引入了泛型,在声明集合的时候就指定集合中存放的是什么类型的对象:

List<String> list = new ArrayList<String>();

那么在获取时就没必要担忧类型的问题,由于泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型,若是不正确会在编译时就会发现错误,而没必要等到运行时才发现错误。由于泛型是在1.5引入的,为了向前兼容,因此会在编译时去掉泛型(泛型擦除),可是咱们仍是能够经过反射API来获取泛型的信息,在编译时能够经过泛型来保证类型的正确性,而没必要等到运行时才发现类型不正确。因为java泛型的擦除特性,若是不生成桥接方法,那么与1.5以前的字节码就不兼容了。如前面的SuperClass中的方法,实际在编译后的字节码以下:

localhost:mikan mikan$ javap -c -v SuperClass.class
Classfile /Users/mikan/Documents/workspace/project/algorithm/target/classes/com/mikan/SuperClass.class
  Last modified 2015-8-7; size 251 bytes
  MD5 checksum 2e2530041f1f83aaf416a2ca3af9b7e3
  Compiled from "SuperClass.java"
public interface com.mikan.SuperClass<T extends java.lang.Object>
  Signature: #7                           // <T:Ljava/lang/Object;>Ljava/lang/Object;
  SourceFile: "SuperClass.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Class              #10            //  com/mikan/SuperClass
   #2 = Class              #11            //  java/lang/Object
   #3 = Utf8               method
   #4 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
   #5 = Utf8               Signature
   #6 = Utf8               (TT;)TT;
   #7 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
   #8 = Utf8               SourceFile
   #9 = Utf8               SuperClass.java
  #10 = Utf8               com/mikan/SuperClass
  #11 = Utf8               java/lang/Object
{
  public abstract T method(T);
    flags: ACC_PUBLIC, ACC_ABSTRACT
    Signature: #6                           // (TT;)TT;
}
localhost:mikan mikan$

经过

Signature: #7   // <T:Ljava/lang/Object;>Ljava/lang/Object;

能够看到,在编译完成后泛型实际上就成了Object了,因此方法实际上成了

public abstract Object method(Object param);

而SubClass实现了SuperClass这个接口,若是不生成桥接方法,那么SubClass就没有实现接口中声明的方法,语义就不正确了,因此编译器才会自动生成桥接方法,来保证兼容性。

4.如何经过桥接方法获取实际的方法

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

声明:本文由MikanMu原创。

连接:https://blog.csdn.net/mhmyqn/article/details/47342577

IT云清

本文同步分享在 博客“IT云清”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索