簡體   English   中英

關於MethodHandle API的一些基本問題

[英]Some basic questions about MethodHandle API

如何通過MethodHandles.lookup()獲取所有聲明的方法? 如何獲取所有聲明的字段?

MethodHandle.invoke()MethodHandle.invokeExact()MethodHandle.invokeWithArguments()什么區別

此外,我將非常感謝有關使用Methodhandle API for Java devloper的教程 我強調,我正在編寫靜態類型語言普通舊Java,我不是JVM開發人員,特別是我對整個字節碼廢話(invokedynamic)並不感興趣。 我想弄清楚如何使用這個新API而不是Java Core API。

EDITED-2:

@Glen Best下面提供了一些我只想提供的參考資料http://www.oraclejavamagazine-digital.com/javamagazine/20130102?pg=52&search_term=methodhandle&doc_id=-1#pg50這正是我想要的。 我發現實際上有一些新的詞匯表。例如, 目標實際上是指MethodHandle(而不是發送的對象),而調用站點實際上是“調用”“函數指針”又稱MethodHandle的代碼。 此外,它是一定要了解MethodHandle API 不更換核心映像API,而不是suplement它。 例如,您無法使用MethodHandle“發現”所有方法,而您需要Core Reflection API。 但是當你“找到”你想要的方法時,你可以切換到MethodHandle,例如,綁定一些參數或者將它的簽名“改變”(改編)為varargs。

編輯:

我仍在努力找出答案。 我寫了一些我想與大家分享的測試。

package alexander.berkovich;

import static org.junit.Assert.assertSame;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.junit.BeforeClass;
import org.junit.Test;

public class MethodHandlerCleanTest {

    public static MethodHandles.Lookup lookup;

    @BeforeClass
    public static void asetUp(){
        lookup = MethodHandles.lookup();
    }

    public static class Check {
        public void primitive(final int i){
        }
        public void wrapper(final Integer i){
        }
    }

    @Test
    public void testPrimitive() throws Throwable {
        Check check = new Check();

        MethodType type = MethodType.methodType(void.class, int.class);

        MethodHandle mh = lookup.findVirtual(Check.class, "primitive", type);
        mh.invokeWithArguments(check, 1);
        mh.invoke(check, (short)2);
        mh.invoke(check, Integer.valueOf(3));

        Method method = Check.class.getMethod("primitive", int.class);
        method.invoke(check, (short)20);
        method.invoke(check, Integer.valueOf(21));

    }

    @Test
    public void testWrapper() throws Throwable {
        Check check = new Check();

        MethodType type = MethodType.methodType(void.class, Integer.class);

        MethodHandle mh = lookup.findVirtual(Check.class, "wrapper", type);
        mh.invoke(check, 2);

        Method method = Check.class.getMethod("wrapper", Integer.class);
        method.invoke(check, 20);

    }

    @SuppressWarnings("unused")
    public static class StaticInnerClass {

        public static String staticName;
        public String name;


        public void foo(){}

        public static void staticFoo(){}

    }

    @Test
    public void testStaticInnerClassStaticField() throws Throwable {
        MethodHandle mhSet = lookup.findStaticSetter(StaticInnerClass.class, "staticName", String.class);
        String expected = "mama";
        mhSet.invoke(expected);

        MethodHandle mhGet = lookup.findStaticGetter(StaticInnerClass.class, "staticName", String.class);
        Object obj = mhGet.invoke();
        String value = (String)obj;
        assertSame(expected, value);

    }

    @Test
    public void testStaticInnerClassField() throws Throwable {
        StaticInnerClass sut = new StaticInnerClass();
        Field f = StaticInnerClass.class.getDeclaredField("name");
        MethodHandle mhSetUnreflect = lookup.unreflectSetter(f); 
        String expectedUnreflect = "unreflect";
        mhSetUnreflect.invoke(sut, expectedUnreflect);


        MethodHandle mhSet = lookup.findSetter(StaticInnerClass.class, "name", String.class);
        String expected = "mama";
        mhSet.invoke(sut, expected);

        MethodHandle mhGet = lookup.findGetter(StaticInnerClass.class, "name", String.class);
        Object obj = mhGet.invoke(sut);
        String value = (String)obj;
        assertSame(expected, value);

    }

    @Test
    public void testStaticInnerClassConstructor() throws Throwable {
        StaticInnerClass sut = new StaticInnerClass();
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findConstructor(StaticInnerClass.class, type);
        mh.invoke();
    }

    @Test
    public void testStaticInnerClassMethod() throws Throwable {
        StaticInnerClass sut = new StaticInnerClass();
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findVirtual(StaticInnerClass.class, "foo", type);
        mh.invoke(sut);
    }

    @Test
    public void testStaticInnerClassStaticMethod() throws Throwable {
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findStatic(StaticInnerClass.class, "staticFoo", type);
        mh.invoke();
    }

    @SuppressWarnings("unused")
    private class InnerClass {
        public String name;

        public void foo(){}

    }
    @Test
    public void testInnerClassField() throws Throwable {
        InnerClass sut = new InnerClass();
        MethodHandle mhSet = lookup.findSetter(InnerClass.class, "name", String.class);
        String expected = "mama";
        mhSet.invoke(sut, expected);

        MethodHandle mhGet = lookup.findGetter(InnerClass.class, "name", String.class);
        Object obj = mhGet.invoke(sut);
        String value = (String)obj;
        assertSame(expected, value);

    }


    @Test
    public void testInnerClassConstructor() throws Throwable {
        MethodType type = MethodType.methodType(void.class, MethodHandlerCleanTest.class);

        //default constructor is private
        Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
        field.setAccessible(true);
        MethodHandles.Lookup trustedLookup = (MethodHandles.Lookup) 
                field
                .get(null);

        MethodHandle mh = trustedLookup.findConstructor(InnerClass.class, type);
        mh.invoke(this);
    }


    @Test
    public void testInnerClassMethod() throws Throwable {
        InnerClass sut = new InnerClass();
        MethodType type = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findVirtual(InnerClass.class, "foo", type);
        mh.invoke(sut);
    }

}

如何通過MethodHandles.lookup()獲取所有聲明的方法? 如何獲取所有聲明的字段?

將java.lang.invoke視為反射(快速執行)的反射擴展(java.lang.reflect) - 即“調用”類依賴於“反射”類。

  • 您可以通過反射(java.lang.Class和java.lang.reflect)獲取對所有方法/構造函數/字段的引用:

     java.lang.Class<?> someClass = ...; // obtain a Class somehow // Returns all constructors/methods/fields declared in class, // whether public/protected/package/private, // but does NOT include definitions from any ancestors: java.lang.reflect.Constructor<?>[] declaredConstructors = someClass.getDeclaredConstructors(); java.lang.reflect.Method[] declaredMethods = someClass.getDeclaredMethods(); java.lang.reflect.Field[] declaredFields = someClass.getDeclaredFields(); // Returns all constructors/methods/fields declared as public members // in the class AND all ancestors: java.lang.reflect.Constructor<?>[] publicInheritedConstructors = someClass.getConstructors(); java.lang.reflect.Method[] publicInheritedMethods = someClass.getMethods(); java.lang.reflect.Field[] publicInheritedFields = someClass.getFields(); 
  • 您可以通過java.lang.invoke.MethodHandles.Lookup將它們轉換為MethodHandles:

     java.lang.invoke.MethodType mt; java.lang.invoke.MethodHandle mh; java.lang.invoke.MethodHandles.Lookup lookup = MethodHandles.lookup(); // process methods for (java.lang.reflect.Method method: declaredMethods) { mh = lookup.unreflect(method); // can call mh.invokeExact (requiring first parameter to be the class' // object instance upon which the method will be invoked, followed by // the methodparameter types, with an exact match parameter and return // types) or // mh.invoke/invokeWithArguments (requiring first parameter to be the // class' object instance upon which the method will be invoked, // followed by the method parameter types, with compatible conversions // performed on input/output types) } // process constructors for (java.lang.reflect.Constructor<?> constructor: declaredConstructors) { mh = lookup.unreflectConstructor(constructor); // can call mh.invokeExact or // mh.invoke/invokeWithArguments } // process field setters for (java.lang.reflect.Field field: declaredFields) { mh = lookup.unreflectSetter(field); // can call mh.invokeExact or // mh.invoke/invokeWithArguments } // process field getters for (java.lang.reflect.Field field: declaredFields) { mh = lookup.unreflectGetter(field); // can call mh.invokeExact or // mh.invoke/invokeWithArguments } 
  • 您可以通過java.lang.reflect確定方法/構造函數/字段的簽名:

     // If generics involved in method signature: Type[] paramTypes = method.getGenericParameterTypes(); Type returnType = method.getGenericReturnType(); // Note: if Class is non-static inner class, first parameter of // getGenericParameterTypes() is the enclosing class // If no generics involved in method signature: Class<?>[] paramTypes = declaredMethod.getParameterTypes(); Class<?> returnType = declaredMethod.getReturnType(); // Note: if Class is non-static inner class, first parameter of // getParameterTypes() is the enclosing class // Same method calls for declaredConstructor 
  • 您可以通過java.lang.reflect確定方法/構造函數/字段是否是靜態的:

     int modifiers = method.getModifiers(); // same method for constructor/field boolean isStatic = java.lang.Modifier.isStatic(modifiers); 

MethodHandle.invoke(),MethodHandle.invokeExact()和MethodHandle.invokeWithArguments()之間的區別是什么?

  • 請參閱http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html#invoke%28java.lang.Object...%29
  • 如果MethodHandle用於非靜態方法,則提供給這些方法的第一個參數是聲明該方法的Class的實例。 在該類的實例上調用該方法(或者在靜態方法的類本身上調用)。 如果Class是非靜態內部類,則第二個參數是封閉/聲明類的實例。 后續參數是方法簽名參數,按順序。
  • invokeExact不對輸入參數執行自動兼容類型轉換。 它要求參數值(或參數表達式)與方法簽名完全匹配,每個參數作為單獨的參數提供,或者所有參數作為數組一起提供(簽名: Object invokeExact(Object... args) )。
  • invoke要求參數值(或參數表達式)與方法簽名類型兼容 - 執行自動類型轉換,每個參數作為單獨的參數提供或者所有參數作為數組一起提供(簽名:Object invoke(Object。 .. args))
  • invokeWithArguments要求參數值(或參數表達式)與方法簽名類型兼容 - 執行自動類型轉換,每個參數在List中提供(簽名: Object invokeWithArguments(List<?> arguments)

關於為Java devloper使用MethodHandle API的教程,我將不勝感激

不幸的是,那里並不多。 您可以嘗試以下方法。 希望我已經給出了足夠的信息:^)

http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html
http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.Lookup.html
http://www.java7developer.com/blog/?p=191
http://www.oraclejavamagazine-digital.com/javamagazine/20130102?pg=52&search_term=methodhandle&doc_id=-1#pg50
http://www.amazon.com/Well-Grounded-Java-Developer-techniques-programming/dp/1617290068

MethodHandle.invoke(),MethodHandle.invokeExact()和MethodHandle.invokeWithArguments()之間有什么區別

由於我也在努力解決這個問題,所以我決定重新審視這個問題並編寫一個示例,准確顯示這些方法之間的語義差異。

主要區別是:

  1. invokeExact 接受精確的參數和返回值,並且接受參數數組。 調用例如方法簽名(Integer,Integer)Integer不允許(Integer,Integer)Integerint參數,但也不允許使用Object參數調用它,即使該對象實際上是Integer類型 - 參數的編譯時類型必須是類整數,而不是運行時實例:

     Object arg = 1; Object[] args = {1,1}; Integer i = (Integer)handle.invokeExact(1,1); // OK Object o = handle.invokeExact(arg,arg); // ERROR handle.invokeExact(args); // ERROR 

  1. invoke自動轉換參數類型和返回類型,並在原始類型和相應的包裝類之間進行轉換。 但它也不接受作為數組的參數。 例如,方法簽名(Integer,Integer)Integer

     Object arg = 1; Object[] args = {1,1}; Integer i = (Integer)handle.invoke(1,1); // OK Object o = handle.invoke(arg,arg); // OK! o = handle.invoke(args); // ERROR 

  1. invokeWithArguments刪除所有這些限制,並且與Method#invoke非常相似 - 您還可以提供一個數組(或java.util.List )作為參數(但這種靈活性會帶來巨大的性能損失)。 例如,方法簽名(Integer,Integer)Integer

     Object arg = 1; Object[] args = {1,1}; Integer i = (Integer)handle.invokeWithArguments(1,1); // OK Object o = handle.invokeWithArguments(arg,arg); // OK o = handle.invokeWithArguments(args); // OK! 

這是一個完整的例子:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class MethodHandleTest {

    public static class TestClass{
        public int test(int a, Integer b){
            return a+b;
        }
    }

    public static void main(String[] args) throws Throwable{
        Method method = TestClass.class.getMethod("test", int.class, Integer.class);
        MethodHandle handle = MethodHandles.lookup().unreflect(method).bindTo(new TestClass());

        int arg_int = 1;
        Integer argInteger = 1;

        Object[] argArray = {1,1};

        //----------------------------
        // MethodHandle#invokeExact() 
        //----------------------------

        // ONLY an exact invocation is allowed for invokeExact:
        int result = (int) handle.invokeExact(arg_int, argInteger); 

        // inexact first argument type -> throws WrongMethodTypeException - "expected (int,Integer)int but found (Integer,Integer)int"
        Exception e = null;
        try {
            result = (int) handle.invokeExact(argInteger,argInteger);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // inexact return type -> throws WrongMethodTypeException - "expected (int,Integer)int but found (int,Integer)Integer"
        try {
            result = (Integer) handle.invokeExact(arg_int,argInteger);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // inexact return type -> throws WrongMethodTypeException - "expected (int,Integer)int but found (int,Integer)Object"
        try {
            Object o = handle.invokeExact(arg_int,argInteger);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // "argObject" is ALSO NOT OK! - the compile time type of the argument must be of class Integer, not the runtime instance!
        // -> throws WrongMethodTypeException - "expected (int,Integer)int but found (int,Object)int"
        Object argObject = argInteger;
        try {
            result = (int) handle.invokeExact(arg_int,argObject);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // Array of the arguments NOT allowed -> throws WrongMethodTypeException - "expected (int,Integer)int but found (Object[])int"
        try {
            result = (int) handle.invokeExact(argArray);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        // But explicit cast of first or second argument is OK
        result = (int) handle.invokeExact((int)argInteger,argInteger);
        result = (int) handle.invokeExact(arg_int,(Integer)arg_int);

        //-----------------------
        // MethodHandle#invoke() 
        //-----------------------

        // invoke() with exact types - OK -> actually calls invokeExact() behind the scenes
        result = (int) handle.invoke(arg_int, argInteger);

        // implicit conversion of inexact arguments and return type -> OK!
        result = (Integer) handle.invoke(argInteger,argInteger); 

        // Object arguments or return type is OK!
        Object o = handle.invoke(argObject,argObject);

        // Array of the arguments NOT allowed -> throws WrongMethodTypeException - "cannot convert MethodHandle(int,Integer)int to (Object[])int"
        try {
            result = (int) handle.invoke(argArray);
        } catch (WrongMethodTypeException ex) {
            e = ex;
        }
        assert e != null;
        e = null;

        //------------------------------------
        // MethodHandle#invokeWithArguments() 
        //------------------------------------

        // invoke() with exact types - OK
        result = (int) handle.invokeWithArguments(arg_int,arg_int);

        // implicit conversion of inexact arguments and return type -> OK
        result = (Integer) handle.invokeWithArguments(argInteger,argInteger); 

        // Object arguments or return type is OK!
        o = handle.invoke(argObject,argObject);

        // Array of the arguments -> OK
        result = (int) handle.invokeWithArguments(argArray);

        // List of arguments possible -> same as calling invokeWithArguments(list.toArray())
        result = (int) handle.invokeWithArguments(Arrays.asList(argArray));


    }
}

正如Balder所說,兩個調用都invoke並且invokeExact不接受作為數組傳入的參數。 (但是,它們接受數組參數。)

int[] args = {1,1};

// handle for Math.addExact(int, int)
Object o = handle.invokeExact(1,1); // OK
Object o = handle.invoke(1,1); // OK
Object o = handle.invokeExact(args); // ERROR
Object o = handle.invoke(args); // ERROR

錯誤總是錯誤的類型異常:

java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(int, int)int to (Object[])int

所以它沒有解壓縮數組。 但是傳入一個需要它的數組:

// handle for Arrays.sort(int[]) 
handle.invokeExact(args); // OK
handle.invoke(args); // OK

正如Balder所說,使用invokeWithArguments()實現所需的行為可能會產生相當大的開銷。

為了從varargs中獲得解壓縮參數列表所需的行為,必須將句柄轉換為spreader:

 // handle for Math.addExact(int, int)
 handle = handle.asSpreader(int[].class, 2);
 handle.invokeExact(args); // OK
 handle.invoke(args); // OK

當然,當數組被定義為泛型時,與顯式參數傳遞帳戶相同的功能:

 Object[] args = new Object[]{1,1};
 // handle for Math.addExact(int, int)
 handle = handle.asSpreader(int[].class, 2);
 handle.invokeExact(args); // ERROR
 handle.invoke(args); // OK

我沒有在電話之間進行任何性能比較。 對於那些感興趣的人來說,擴展這個基准是非常簡單的: http//rick-hightower.blogspot.de/2013/10/java-invoke-dynamic-examples-java-7.html

細節:

基本上invokeWithArguments做類似的事情,但每次調用都這樣做:

 public Object invokeWithArguments(Object... arguments) throws Throwable {
    MethodType invocationType = MethodType.genericMethodType(arguments == null ? 0 : arguments.length);
    return invocationType.invokers().spreadInvoker(0).invokeExact(asType(invocationType), arguments);
}

因此,創建一個spreader invokeExact它,很可能會產生與invokeinvokeExact類似的性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM