3

I'm trying to execute annotated method within main method in App.java with LambdaMetafactory.metafactory():

import com.drfits.annotation.RunMethod;
import com.drfits.transfer.Transfer;
import com.drfits.transfer.TransferExecutor;
import com.drfits.transfer.TransferExecutorImpl;
import com.drfits.transfer.TransferImpl;

import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.Function;

/**
 * Application for execute annotated method
 */
public class App {

    public static void main(String[] args) {
        Transfer transfer = new TransferImpl("Hello, World!");
        Method[] methods = TransferExecutorImpl.class.getMethods();
        for (Method method : methods) {
            RunMethod annotation = method.getAnnotation(RunMethod.class);
            if (annotation != null) {
                try {
                    MethodHandles.Lookup lookup = MethodHandles.lookup();
                    MethodHandle methodHandle = lookup.unreflect(method);
                    MethodType invokedType = MethodType.methodType(Function.class);
                    MethodType functionMethodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes());

                    // Lambda which can be executed
                    TransferExecutor transferExecutor= new TransferExecutorImpl();
                    Function<Transfer, Void> commonLambda = transferExecutor::execute;
                    commonLambda.apply(transfer);

                    // Lambda constructed manually
                    Function<Transfer, Void> constructedLambda = (Function) LambdaMetafactory.metafactory(
                            lookup,
                            "apply",
                            invokedType,
                            functionMethodType,
                            methodHandle,
                            methodHandle.type()).getTarget().invokeExact();
                    constructedLambda.apply(transfer);
                } catch (Throwable t) {
                    System.out.println(t.getMessage());
                }
            }
        }
    }
}

If I'm trying to execute this code it'll throw exception:

Incorrect number of parameters for instance method invokeVirtual com.drfits.transfer.TransferExecutorImpl.execute:(Transfer)void; 0 captured parameters, 1 functional interface method parameters, 1 implementation parameters
Gili
  • 86,244
  • 97
  • 390
  • 689
Evgeniy Fitsner
  • 946
  • 9
  • 14

2 Answers2

4

Using the code

TransferExecutor transferExecutor= new TransferExecutorImpl();
Function<Transfer, Void> commonLambda = transferExecutor::execute;

you are binding the Function to a particular instance of TransferExecutor. Your dynamic creation code lacks an instance for the invocation of the instance method TransferExecutorImpl.execute. That’s what the exception tries to tell you.

An instance method needs a target instance to be invoked on, hence your target method has a functional signature of (TransferExecutor,Transfer)→Void.

You can either, create a BiFunction<TransferExecutor,Transfer, Void> out of this method or bind an instance to it like with your transferExecutor::execute method reference. For the latter

  • change the invoked type to receive an instance of TransferExecutor

    MethodType invokedType = MethodType.methodType(
                                 Function.class, TransferExecutorImpl.class);
    
  • provide the argument at the invocation:

    … .getTarget().invokeExact((TransferExecutorImpl)transferExecutor);
    

Note that there is still a subtle difference. The statement Function<Transfer, Void> commonLambda = transferExecutor::execute; refers to the interface method while the method you have identified via your annotation is the method declared in TransferExecutorImpl.

Regarding binding captured values, see this and that answer for more explanation and examples.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
1

The error is trying to tell you are not passing another parameters. It expects one, but you are not passing it one.

I suggest you look at the parameters you are passing and compare them to the parameters actually passed when building a lambda.

Write what you are trying to do and a lambda first and see what parameters you should be passing.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130