Some basic questions about MethodHandle API

喜你入骨 提交于 2019-12-17 21:57:00

问题


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

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

Also, I will be appreciate for tutorial about using MethodHandle API for Java devloper. I emphasize, I am programming on statically typed language plain old Java and I am not JVM developer, particularly I am not interesting with whole byte-code crap (invokedynamic). I want to figure out how can I use this new API instead of Java Core API.

EDITED-2:

@Glen Best below provided some refferences I want to provide only one http://www.oraclejavamagazine-digital.com/javamagazine/20130102?pg=52&search_term=methodhandle&doc_id=-1#pg50 This is exactly what I was looking for. I figured out that actually there some new vocabalry.. For example, by target is actually meant MethodHandle (and not object to make dispatch upon) and call site is actually code that "invokes" "function pointer" aka MethodHandle. Also it is essential to understand that MethodHandle API is not replacement for Core Reflection API rather than suplement it. For example, you can't "discover" all methods with MethodHandle you need Core Reflection API. But when you "find" you desired method you can switch to MethodHandle and for example, bound some its parameters or "change" (adapt) it's signature to varargs for example.

EDITED:

I am still trying to figure out answer. I wrote some tests that I want to share with all.

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

}


回答1:


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




回答2:


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


    }
}



回答3:


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.



来源:https://stackoverflow.com/questions/16005824/some-basic-questions-about-methodhandle-api

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!