Some basic questions about MethodHandle API

后端 未结 3 1920
攒了一身酷
攒了一身酷 2020-12-13 05:02

How can I obtain all declared method through MethodHandles.lookup()? How can I obtain all declared fields?

What is difference betweeen MethodHandl

相关标签:
3条回答
  • 2020-12-13 05:40

    What is difference betweeen MethodHandle.invoke(), MethodHandle.invokeExact() and MethodHandle.invokeWithArguments()

    Since I also struggled with this, I decided to revisit this question and write an example that shows exactly what the semantic differences between these methods are.

    The major differences are:

    1. invokeExact only accepts exact arguments and return types, and does not accept arguments as an array. Calling e.g. method signature (Integer,Integer)Integer with an int argument is not allowed, but also calling it with an Object argument is not allowed, even if the object is actually of type Integer - the compiletime type of the argument must be of class Integer, not the runtime instance:

      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 automatically converts argument types and return types and also converts between primitive types and the corresponding wrapper classes. But it does also not accept arguments as an array. E.g. for method signature (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 removes all these restrictions and works very similar to Method#invoke - you can also supply an array (or a java.util.List) as an argument (but this flexibility comes with a huge performance penalty). E.g. for method signature (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!
      

    Here a complete example:

    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));
    
    
        }
    }
    
    0 讨论(0)
  • 2020-12-13 05:43

    How can I obtain all declared method through MethodHandles.lookup()? How can I obtain all declared fields?

    Think of java.lang.invoke as a (fast performing) extension to reflection (java.lang.reflect) - i.e. "invoke" classes are dependent upon "reflection" classes.

    • You obtain reference to all methods/constructors/fields via reflection (java.lang.Class and 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();
      
    • You convert them to MethodHandles via java.lang.invoke.MethodHandles.Lookup:

      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 
      }
      
    • You can determine if the signature of methods/constructors/fields via 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
      
    • You can determine if the methods/constructors/fields are static via java.lang.reflect:

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

    What is difference betweeen MethodHandle.invoke(), MethodHandle.invokeExact() and MethodHandle.invokeWithArguments()?

    • see http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html#invoke%28java.lang.Object...%29
    • If the MethodHandle is for a non-static method, the first parameter provided to these methods is an instance of the Class which declares the method. The method is invoked on this instance of the class (or on the Class itself for static methods). If the Class is a non-static inner class, the second parameter is an instance of the enclosing/declaring class. The subsequent parameters are the method signature parameters, in order.
    • invokeExact does not do automatic compatible type conversion on input parameters. It requires parameter values (or parameter expressions) to be an exact type match to the method signature, with each parameter provided as a separate argument OR all arguments provided together as an array (signature: Object invokeExact(Object... args)).
    • invoke requires the parameter values (or parameter expressions) to be type compatible match to the method signature - automatic type conversions are performed, with each parameter provided as a separate argument OR all arguments provided together as an array (signature: Object invoke(Object... args))
    • invokeWithArguments requires the parameter values (or parameter expressions) to be type compatible match to the method signature - automatic type conversions are performed, with each parameter provided within a List (signature: Object invokeWithArguments(List<?> arguments))

    I will be appreciate for tutorial about using MethodHandle API for Java devloper

    There's not much out there, unfortunately. You could try the following. Hopefully, I've given enough info above :^)

    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

    0 讨论(0)
  • 2020-12-13 05:45

    As Balder said both calls invoke and invokeExact do not accept arguments passed in as an array. (However, they do accept array arguments.)

    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
    

    The error is always a wrong type exception:

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

    So it does not unpack the array. But passing in an array where it is required works:

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

    As Balder said, implementing the desired behavior with invokeWithArguments() may incur a quite substantial overhead.

    In order to get the desired behavior for unpacking argument lists as known from varargs, one has to turn the handle into a spreader:

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

    Of course, the same functionality as for explicit argument passing accounts again when the array is defined to be generic:

     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
    

    I have not conducted any performance comparison between the calls. For those interested, it is quite straight forward to extend this benchmark: http://rick-hightower.blogspot.de/2013/10/java-invoke-dynamic-examples-java-7.html

    Details:

    Essentially invokeWithArguments does similar things but does so for every call:

     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);
    }
    

    So creating a spreader once and reusing it, will most likely yield similar performance as invoke and invokeExact.

    0 讨论(0)
提交回复
热议问题