轉載 java中什麼是bridge method(橋接方法)

看spring-mvc的源碼的時候,看到在解析handler方法時,有關於獲取橋接方法代碼,不明白什麼是橋接方法,通過查找資料,終於理解了什麼是橋接方法。
什麼是橋接方法html

橋接方法是 JDK 1.5 引入泛型後,爲了使Java的泛型方法生成的字節碼和 1.5 版本前的字節碼相兼容,由編譯器自動生成的方法。java

咱們能夠經過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。spring

有以下3個問題:spring-mvc

    何時會生成橋接方法
    爲何要生成橋接方法
    如何經過橋接方法獲取實際的方法mvc

何時會生成橋接方法oracle

那何時編譯器會生成橋接方法呢?能夠查看JLS中的描述http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.4.5。app

就是說一個子類在繼承(或實現)一個父類(或接口)的泛型方法時,在子類中明確指定了泛型類型,那麼在編譯時編譯器會自動生成橋接方法(固然還有其餘狀況會生成橋接方法,這裏只是列舉了其中一種狀況)。以下所示:jvm

    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的字節碼:this

    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類型的,若是指定其餘類型,那麼在編譯時就會錯誤,這樣就把類型檢查從運行時提早到了編譯時。這就是泛型的好處。

爲何要生成橋接方法

上面看到了編譯器在何時會生成橋接方法,那爲何要生成橋接方法呢?

在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就沒有實現接口中聲明的方法,語義就不正確了,因此編譯器纔會自動生成橋接方法,來保證兼容性。

如何經過橋接方法獲取實際的方法

咱們在經過反射進行方法調用時,若是獲取到橋接方法對應的實際的方法呢?能夠查看spring中org.springframework.core.BridgeMethodResolver類的源碼。其實是經過判斷方法名、參數的個數以及泛型類型參數來獲取的。 --------------------- 原文:https://blog.csdn.net/mhmyqn/article/details/47342577?utm_source=copy

相關文章
相關標籤/搜索