MethodHandle(方法句柄)系列之三:invoke和invokeExact的區別


  先把代碼貼上來,用的是一樣的代碼
/**
 * 
 * @author LiuYeFeng<897908343@qq.com>
 * @date 2015年4月8日 下午10:41:13
 * @CopyRight 2015 TopView Inc
 * @version V1.0
 */
public class MethodHandleTest {
 
    public MethodHandle getHandler() {
        MethodHandle mh = null;
        MethodType mt = MethodType.methodType(String.class, int.class, int.class);
        MethodHandles.Lookup lk = MethodHandles.lookup();
         
        try {
            mh = lk.findVirtual(String.class, "substring", mt);
        } catch (Throwable e) {
            e.printStackTrace();
        }
         
        return mh;
    }
     
    public static void main(String[] args) throws Throwable {
        MethodHandle mh = new MethodHandleTest().getHandler();
        String str = "hello world";
         
        Object result1 = mh.invoke(str, 1, 3);
        Object result2 = (String) mh.invokeExact(str, 1, 3);
//      Object result2 = mh.invokeExact(str, new Integer(1), 3); 
        /**
         * 上面這句方法執行時報錯,因為方法類型為String.class, int.class, int.class
         * 而返回的類型為Object,與聲明中為String不符合
         * 其中第二個參數類型為Integer,與聲明中為int不符合,則類型適配不符合,系統報錯。
         */
         
        System.out.println("result 1:" + result1);
        System.out.println("result 1:" + result2);
    }
}

    invoke和invokeExact方法的區別,從名字上來看,明顯是后者准確性更高,或者說要求更嚴格。invokeExact方法在調用時要求嚴格的類型匹配,方法的返回值類型也在考慮范圍之內,如同上面代碼中注釋掉的一句。如果把Object result2 = (String) mh.invokeExact(str, 13);中的(String)去掉類型轉換的話,在調用的時候該方法會認為返回值是Object類型而不是String類型,然后系統報錯。

       與invokeExact方法不同,invoke方法允許更加松散的調用方式。它會嘗試在調用的時候進行返回值和參數類型的轉換工作。這是通過MethodHandle類的asType方法來完成的,asType方法的作用是把當前方法句柄適配到新的MethodType上面,並產生一個新的方法句柄。當方法句柄在調用時的類型與其聲明的類型完全一致的時候,調用invoke方法等於調用invokeExact方法;否則,invoke方法會先調用asType方法來嘗試適配到調用時的類型。如果適配成功,則可以繼續調用。否則會拋出相關的異常。這種靈活的適配機制,使invoke方法成為在絕大多數情況下都應該使用的方法句柄調用方式。
      進行類型匹配的基本規則是對比返回值類型和每個參數的類型是否都可以相互匹配。假設源類型為S,目標類型為T,則基本規則如下:
        1、可以通過java的類型轉換來完成,一般從子類轉成父類,比如從String到Object類型;
        2、可以通過基本類型的轉換來完成,只能將類型范圍的擴大,比如從int切換到long;
        3、可以通過基本類型的自動裝箱和拆箱機制來完成,例如從int到Integer;
        4、如果S有返回值類型,而T的返回值類型為void,則S的返回值會被丟棄。
        5、如果S的返回值是void,而T的返回值是引用類型,T的返回值會是null;
        6、如果S的返回值是void,而T的返回值是基本類型,T的返回值會是0;
 
    第1、2、3條很好理解,第4、5、6條感覺原理都一樣,我就用個新例子說明。
public class MethodHandleTest {
 
    public MethodHandle getHandler() {
        MethodHandle mh = null;
        MethodType mt = MethodType.methodType(void.class);
        MethodHandles.Lookup lk = MethodHandles.lookup();
         
        try {
            mh = lk.findVirtual(MethodHandleTest.class, "print", mt);
        } catch (Throwable e) {
            e.printStackTrace();
        }
         
        return mh;
    }
     
    public void print() {
        System.out.println("print");
    }
     
    public static void main(String[] args) throws Throwable {
        MethodHandleTest mht = new MethodHandleTest();
        MethodHandle mh = mht.getHandler();
         
        int result1 = (int) mh.invoke(mht);
        Object result2 = mh.invoke(mht);
         
        System.out.println("result 1:" + result1);
        System.out.println("result 2:" + result2);
    }
}

 

程序輸出結果是:
print
print
result 1:0
result 2:null
 
 
參考資料:《java程序員修煉之道》、《深入理解java7核心技術與最佳實踐》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM