10

I get an exception when I call metafactory. It says:

java.lang.invoke.LambdaConversionException:
    Incorrect number of parameters for instance method
        invokeVirtual my.ExecuteTest$AProcess.step_1:()Boolean;
    0 captured parameters, 
    0 functional interface method parameters, 
    0 implementation parameters

I do not understand all from the documentation of LambdaMetafactory.metafactory. I have problems figuring out the correct parameters:

  • MethodHandles.Lookup caller -- thats easy
  • String invokedName -- I am fairly certain here
  • MethodType invokedType -- whats this?
  • MethodType samMethodType -- err... not sure here
  • MethodHandle implMethod -- that's fine
  • MethodType instantiatedMethodType -- whats this, again? Second time?

So it boils down to what are the differences between:

  • MethodType invokedType
  • MethodType samMethodType
  • MethodType instantiatedMethodType

My code is like this:

package my;

import java.lang.invoke.*;
import java.lang.reflect.Method;

public class Execute {

  public interface ProcessBase {};

  @FunctionalInterface
  public interface Step {
    Boolean apply();
  }

  public Step getMethodFromStepid(ProcessBase process, int stepid) {
    try {
      // standard reflection stuff
      final MethodHandle unreflect = caller.unreflect(method);
      final String mname = "step_"+stepid;
      // new java8 method reference stuff
      final Method method = process.getClass().getMethod(mname);
      final MethodType type=MethodType.methodType(Boolean.class);
      final MethodType stepType=MethodType.methodType(Step.class);
      final MethodHandles.Lookup caller = MethodHandles.lookup();
      final CallSite site = LambdaMetafactory.metafactory(
          caller, "apply", stepType, type, unreflect, type); // damn
      // convert site to my method reference
      final MethodHandle factory = site.getTarget();
      final Step step = (Step) factory.invoke();
      return step;
    } catch (Throwable throwable) {
      throw new RuntimeException(throwable);
    }
  }
}

with the tests

package my;

import org.junit.Test;
import static org.junit.Assert.*;

public class ExecuteTest {

  private class AProcess implements Execute.ProcessBase {
    public Boolean step_1() { return true; }
    public Boolean step_2() { return false; }
  }

  @Test
  public void getMethodFromStepid() throws Exception {
    final AProcess process = new AProcess();
    {
      final Execute.Step methodRef = instance.getMethodFromStepid(process, 1);
      final boolean result = methodRef.apply();
      assertTrue(result);
    }
    {
      final Execute.Step methodRef = instance.getMethodFromStepid(process, 2);
      final boolean result = methodRef.apply();
      assertFalse(result);
    }
  }

  private final Execute instance = new Execute();

}
towi
  • 21,587
  • 28
  • 106
  • 187
  • 1
    What exactly are you trying to do? LMF is largely a tool for compiler writers -- think of it as the "lambda creation bytecode." – Brian Goetz Sep 23 '16 at 15:57
  • @BrianGoetz I have a sort of state machine where each state is programmed by java code and the edges are currently programmed via method-names and reflection, ie. `void step_3() { if(x) outgoing("step_4"); }`. Of course you can make mistakes here you only notice at runtime. So I thought using methd refs would be safer, ie. `void step_3() { if(x) outgoing(::step_4); }`. Then the works on method-refs instead of strings. To support the legacy code I need to go from step-number `int i` (e.g. `5`) to the method-ref `step_i`, eg. `step_5`. – towi Sep 25 '16 at 17:22
  • 1
    Why wouldn't you just use AProcess::step_1 as a method reference from your test source, and be done? I think you're making this too complicated.... – Brian Goetz Sep 25 '16 at 17:32
  • @BrianGoetz The state of the machine is persisted. Currently the persistence is by storing `i` away. Then I later fetch the `i` back I have to get from `i` to the method reference to execute it. I don't think I over-complicate things but maybe I am missing something. The engine itself can work on method-refs only, sure, that's my plan. But to feed the state machine engine from states in the DB I have to generate method-refs from that state. If I could store method-refs or whole closures in the DB, that would me something, though. ;-) (happy python-stackless programmers...) – towi Sep 26 '16 at 17:43

1 Answers1

7

The first three parameters are not special to lambda expressions, but standard arguments to bootstrap methods of invokedynamic instruction. The lookup parameter encapsulates the caller’s context, the invokedName and invokedType parameters represent the name and type of the invokedynamic instruction.

It’s up to the bootstrap method to assign more semantic do it. Since in this context, the purpose of this instruction is produce a lambda expression instance, it will consume captured values and produce an interface instance. So the invokedType will have parameter types reflecting the type of captured values or be parameter-less for non-capturing lambdas and have a return type matching the desired functional interface. The invokedName is used to specify the functional interface’s method name, which is unusual as it’s not actually invoked here, but since the invoked name has no other meaning otherwise, this parameter is reused here.

The samMethodType is the signature of the functional interface’s method to implement (on the byte code level), which is identical to instantiatedMethodType as long as, e.g. Generics is not involved. Otherwise, samMethodType will be subject to type erasure whereas instantiatedMethodType incorporates the actual type arguments, e.g. to implement a Function<String,Integer>

  • invokedType will have a return type of Function
  • samMethodType will be (Object)Object
  • instantiatedMethodType will be (String)Integer

Note that for your specific case, the types are basically correct, but since you want to invoke the target method on the provided process instance, you have to bind it to the lambda instance (you didn’t even try). Unfortunately, you didn’t make clear what kind of actual problem you have (i.e. that you are getting a LambdaConversionException) in your question, so I didn’t notice the problem before.

As said above, the invokedType must contain the types of the values to capture as parameter types. Then, you have to pass the actual process instance to the invoke call. As the name suggests, invokedType must match the type of invoke:

public Step getMethodFromStepid(ProcessBase process, int stepid) {
    try {
            // standard reflection stuff
            final String mname = "step_"+stepid;
            final Method method = process.getClass().getMethod(mname);
            // new java8 method reference stuff
            final MethodType type=MethodType.methodType(Boolean.class);
            // invokedType: bind process, generate Step
            final MethodType stepType=MethodType.methodType(Step.class,process.getClass());
            final MethodHandles.Lookup caller = MethodHandles.lookup();
            final MethodHandle unreflect = caller.unreflect(method);
            final CallSite site = LambdaMetafactory.metafactory(
                caller, "apply", stepType, type, unreflect, type);
            // convert site to my method reference
            final MethodHandle factory = site.getTarget();
            // pass the value to bind and get the functional interface instance
            final Step step = (Step)factory.invoke(process);
            return step;
      } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
      }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Ok, that gets me further. in my case `invokedType` will be `Function` (becaue I call `Boolean step()` generated by `MethodType.methodType(Function.class);`, `samMethodType`... err... `MethodType.methodType(Object.class)` because of the `Function` type erasure?? And `instantiatedMethodType` will be generated by `MethodType.methodType(...)` ...? Actually, I can not see where `(String)Integer` comes from in my example. Also, how do I generate `(A)B` with a `methodType()` call? Is it, as you say `samMethodType` again, i.e. `MethodType.methodType(Object.class)`? – towi Sep 23 '16 at 07:29
  • 2
    No, you generate an instance of `Step`, hence your `invokedType` is `()Step`, which you already create correctly via `MethodType.methodType(Step.class)`. Since this interface is not generic, the `samMethodType` and `instantiatedMethodType` are both `()Boolean`, which you already create correctly via `MethodType.methodType(Boolean.class)`. There’s no need to change anything. – Holger Sep 23 '16 at 08:12
  • 4
    Don’t get confused by the `(ParameterTypes)ReturnType` syntax. You have to change the order when using `MethodType.methodType(ReturnType [,ParameterTypes])`, not that it matters for signatures without parameters. Or you use `MethodType.fromMethodDescriptorString("()Ljava/lang/Boolean;")` which follows the `(ParameterTypes)ReturnType` syntax, however, it doesn’t make the code more readable. – Holger Sep 23 '16 at 08:16
  • (2) I see. Indeed I wasn't saying I wanted to have a Lambda, because I didn't know. But now that you say it, its obvious. Now I understand the `invoke`-call, and it becomes obvious, I need to pass the `this`. I think I gained a lot of understanding now. – towi Sep 23 '16 at 09:31