3

I am trying to use LambdaMetafactory to replace reflection,but I have a problem.If I use a specific class then it works well,just like this:

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType type = MethodType.methodType(ResponseMsg.class,Map.class);

        MethodHandle mh = lookup.findVirtual(TestService.class,"testMethod",type);

        TestService ins = TestService.getInstance();

        MethodHandle factory = LambdaMetafactory.metafactory(
                lookup, "apply", MethodType.methodType(Function.class,TestService.class),
                type.generic(), mh, type).getTarget();

        factory.bindTo(ins);

        Function lambda = (Function) factory.invokeExact(ins);

But if I use Class<?> to replace the specific class ,then it won't work,just like this:

    public static Function generateLambda(@NotNull Class<?> cls,@NotNull String method) {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodType type = MethodType.methodType(RETURN_TYPE,PARM_TYPE);

    try {
        MethodHandle mh = lookup.findVirtual(cls,method,type);
        Object instance = getInstance(cls);
        if(instance == null) {
            return null;
        }
        MethodHandle factory = LambdaMetafactory.metafactory(
                lookup, "apply", MethodType.methodType(Function.class,cls),
                type.generic(), mh, type).getTarget();

        factory.bindTo(cls.cast(instance));

        return (Function) factory.invokeExact(cls.cast(instance));
    } catch (Throwable e) {
        logger.error("get Function fail, cause :" ,e);
        return null;
    }
}

Here is the exception:

java.lang.invoke.WrongMethodTypeException: expected (TestService)Function but found (Object)Function
    at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:298)
    at java.lang.invoke.Invokers.checkExactType(Invokers.java:309)
    at com.utils.cache.ClassUtils.generateLambda(ClassUtils.java:182)

The Line 182 is:

return (Function) factory.invokeExact(cls.cast(instance));

I know just use static method can solve this problem,but I want to know is there any other way to solve it without changing the non-static to static.

Here is the getInstance:

 private static Object getInstance(@NotNull Class<?> cls) {
        try {
            Method getInstanceMethod = cls.getDeclaredMethod("getInstance");
            return getInstanceMethod.invoke(null);
        } catch (Exception e) {
            logger.error("get instance fail, cause :" ,e);
            return null;
        }
    }

In this method,I use reflection to find the static method getInstance in Class,and return a instance,it is just a simple singleton.

liyixin
  • 33
  • 3
  • Can you please post your ```getInstance(cls);``` method code ? – Schidu Luca Nov 05 '18 at 09:23
  • I haved posted that method – liyixin Nov 05 '18 at 09:31
  • 1
    `invokeExact` is special method, it uses polymorphic signature and is used as invoke dynamic where types of arguments are important, so call `.invokeExact("string")` is different that `.invokeExact((Object) "string")`, this is why MethodHandles can be fast for static typed code - as it does not need to validate and cast all that stuff each time. – GotoFinal Nov 05 '18 at 11:57
  • Also, why you want to replace reflections with this? cached reflections are fast too, otherwise, you can use `.invokeWithArguments` and modify method handle signature to expect different types (but this will reduce performance by very tiny bit). In dynamic usage there isn't bigger difference between using cached method instance vs MethodHandle – GotoFinal Nov 05 '18 at 11:58
  • What happens if you move away from the wildcard, i.e. `public static Function generateLambda(@NotNull Class cls,... T instance = getInstance(cls); ...`? (Probably nothing due to type erasure, but this way it's somewhat (type-)safer code.) – JimmyB Nov 05 '18 at 13:26
  • The invokeExact needs strict type matching,so I change invokeExact to invoke and this problem was solved...Thanks a lot @GotoFinal – liyixin Nov 05 '18 at 14:39
  • @GotoFinal when you use the generated `Function` instance multiple times, it may outperform any reflective solution, this is why this facility was added as the backend for lambda expressions and method handles in the first place. – Holger Nov 05 '18 at 17:23

1 Answers1

1

The problem is that you are using

factory.bindTo(ins);
Function lambda = (Function) factory.invokeExact(ins);

resp.

factory.bindTo(cls.cast(instance));
return (Function) factory.invokeExact(cls.cast(instance));

Calling bindTo creates a MethodHandle, whose first parameter is bound to the specified object instance, however, you are ignoring the new MethodHandle. Therefore, you need to specify the instance again as an argument when invoking the unbound handle.

For this invocation, the compile-time type matters. In the first example, the compile-time type of the argument is correct, hence the invocation has the right signature (TestService)Function.

In the second example, the compile-time type of instance is Object, hence the signature compiled into the byte code will be (Object)Function, which is not an exact match. Using cls.cast(…) does not help, as that will perform a runtime check and assert that generic types match, if you used a type variable here, but both is irrelevant to the byte code of the invokeExact call.

You have two options. You can simply use invoke instead, which allows type conversions during the invocation (sacrificing a bit performance)

// unused factory.bindTo call omitted
return (Function) factory.invoke(instance); // noneffective cls.cast omitted

or you change the code to do what seems to be originally intended, bind the first argument before the invocation:

factory = factory.bindTo(instance);
return (Function)factory.invokeExact();

since for the pre-bound method handle, no argument is needed, you have an exact call again (bindTo is not signature polymorphic, hence, will check the type of the argument only at runtime).

You could also write this as a one-liner

return (Function)factory.bindTo(instance).invokeExact();
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks a lot,you complete solved my problem.But I want to say the second option must be written in one line:`return (Function)factory.bindTo(instance).invokeExact();`or it will throw exception. – liyixin Nov 06 '18 at 03:55
  • The crucial point of the second option is not to forget assigning the result of `bindTo` to `factory` again when using two statements. When chaining both calls, the `invokeExact` call will be performed on the result of `bindTo` automatically. – Holger Nov 06 '18 at 07:09