Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Types in a LambdaMetaFactory

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

}
like image 791
towi Avatar asked Sep 22 '16 13:09

towi


People also ask

What is LambdaMetafactory?

java.lang.Object. java.lang.invoke.LambdaMetafactory. public final class LambdaMetafactory extends Object. Methods to facilitate the creation of simple "function objects" that implement one or more interfaces by delegation to a provided MethodHandle , possibly after type adaptation and partial evaluation of arguments.

How Lambda expression works in java?

Lambda Expressions were added in Java 8. A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.


1 Answers

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);
      }
}
like image 124
Holger Avatar answered Sep 20 '22 19:09

Holger