Search code examples
javareflectionlambda-metafactory

Java LambdaMetaFactory Method call with multiple vargs to avoid Reflection


I am currently using Java reflection

I don't have any problem doing it with reflection . I learned about LambdaMetaFactory has better performance than reflection .. There is examples about getter and setter .. but there is no example for multiple parameterized method like doSomethig(String a, String b, int c) ;

here is what i am doing on reflection

@Override
public  T invokeReturn(final Object instance, final Object... args) throws Exception {

    try {
        final Method mtd = this.getMethod();
        mtd.setAccessible(getModifierAccessType() != ModifierAccessType.PUBLIC);
        final Object result = mtd.invoke(instance, args);
        if (getModifierAccessType() != ModifierAccessType.PUBLIC) {
            mtd.setAccessible(false);
        }
        return (T) result;
    } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
        logger.error("Error while Invoking Method", ex);
        throw new Exception(ex.getCause());
    }

}

i want to add another method which supports LambdaMetaFactory here what iam trying

@Override
public <T> T callReturn(Object instance, Object... args) throws Exception {
    try {
        if (returnMethod == null) {
            final MethodHandles.Lookup lookup = MethodHandles.lookup();
            MethodHandle methodHandle = lookup.unreflect(method);
            MethodType fncType = this.mtdType();
            MethodType type = this.callMethodType();
            this.returnMethod = LambdaMetafactory.metafactory(lookup, "call", fncType, type, methodHandle, methodHandle.type()).getTarget();
        }

        switch (this.fncType()) {
            case Function: {
                MethodFunction<T> result = (MethodFunction<T>) this.returnMethod.invoke();
                return result.call(instance);
            }
            case FunctionArgs: {
                MethodFunctionArgs<T> result =  (MethodFunctionArgs<T>) this.returnMethod.invoke();
                 Object[] invokeParams = this.getInvokeParams(instance, args);
                return result.call(invokeParams);
            }
            case Void: {
                final VoidFunction result = (VoidFunction) this.returnMethod.invoke();
                result.call(instance);
                return null;
            }
            default: {
                final VoidFunctionArgs result = (VoidFunctionArgs) this.returnMethod.invoke();
                result.call(instance);
                return null;
            }
        }

    } catch (Throwable ex) {
        throw new Exception(ex);
    }
}

without arguments I dont have any problem on switch cases default and Function, but with arguments i can't run it Here is My MethodFunctionArgs @FunctionalInterfaces

@FunctionalInterface
public interface MethodFunctionArgs<T> {

    T call(Object... params) ;
    ///tried too no success
    //T call(Object instance, Object... params) ;
    //VoidFunctionArgs  
    ///void call(Object instance, Object... params);
}

Anyway to do that? there is not a lot of example or tutorial all is only getter and setter maybe there is a way to create dynamic @functionalinterface with varags?

thanx for help...


Solution

  • You can’t bind an interface with a varargs method to an arbitrary target method, as then, the interface would promise to handle an arbitrary number of arguments while the actual implementation method only accepts a fixed number of arguments. Hence, the following does not compile:

    public interface MethodFunctionArgs<T> {
        T call(Object... params);
        static String someMethod(String arg1, int arg2) { return ""; }
        // does not work
        MethodFunctionArgs<String> func = MethodFunctionArgs::someMethod;
    }
    

    What you can do, is the other way round, an implementation method that can handle an arbitrary number of arguments can also handle a request with specific arguments:

    public interface MethodFunctionArgs<T> {
        T call(String arg1, int arg2);
        static String someMethod(Object... params) { return ""; }
        // no problem
        MethodFunctionArgs<String> func = MethodFunctionArgs::someMethod;
    }
    

    But it must be mentioned that the LambdaMetaFactory is not capable of handling the varargs processing. The compiler helps here by inserting a synthetic helper method, so the compiled code is equivalent to

    public interface MethodFunctionArgs<T> {
        T call(String arg1, int arg2);
        static String someMethod(Object... params) { return ""; }
        // no problem
        MethodFunctionArgs<String> func = (arg1,arg2) -> someMethod(new Object[]{arg1, arg2});
    }
    

    But when you have a functional interface with varargs, you are already paying a lot of the typical Reflection costs (boxing, and creating and filling an array) before even invoking the interface method. You may still test the performance of Method.invoke compared to MethodHandle.invoke:

    private MethodHandle handle;
    public Object callReturn(Object... args) throws Exception {
        try {
            if(handle == null) {
                final MethodHandles.Lookup lookup = MethodHandles.lookup();
                MethodHandle h = lookup.unreflect(this.getMethod());
                handle = h.asType(h.type().generic())
                          .asSpreader(Object[].class, h.type().parameterCount());
            }
            return handle.invokeExact(args);
        } catch (Throwable ex) {
            throw new Exception(ex);
        }
    }
    

    To get a performance benefit from the LambdaMetaFactory, you need a specific interface with a matching functional signature.

    interface SpecialFunction {
        Map<String,SpecialFunction> PREDEFINED = getMap();
    
        String call(int i, double d, String s);
    
        static String method1(int i, double d, String s) {
            return "method1("+i+", "+d+", "+s+')';
        }
        static String method2(int i, double d, String s) {
            return "method2("+i+", "+d+", "+s+')';
        }
        static String method3(int i, double d, String s) {
            return "method3("+i+", "+d+", "+s+')';
        }
        /* private  (with Java 9) */ static Map<String,SpecialFunction> getMap() {
            Map<String,SpecialFunction> map = new HashMap<>();
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            MethodType invoked = MethodType.methodType(SpecialFunction.class);
            MethodType func = MethodType.methodType(String.class,
                                                    int.class, double.class, String.class);
            final int mod = Modifier.PUBLIC|Modifier.STATIC;
            for(Method m: SpecialFunction.class.getDeclaredMethods()) try {
                MethodHandle target = lookup.unreflect(m);
                if((m.getModifiers()&mod) == mod && target.type().equals(func))
                    map.put(m.getName(), (SpecialFunction)LambdaMetafactory.metafactory(
                        lookup, "call", invoked, func, target, func).getTarget().invoke());
            } catch(Throwable ex) {
                throw new ExceptionInInitializerError(ex);
            }
            return Collections.unmodifiableMap(map);
        }
    }