LambdaMetaFactory 中的类型

Types in a LambdaMetaFactory

我在调用 metafactory 时遇到异常。它说:

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

LambdaMetafactory.metafactory的文档我没看懂。我在确定正确参数时遇到问题:

所以归结起来有什么区别:

我的代码是这样的:

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

通过测试

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

}

前三个参数不是lambda表达式特有的,而是invokedynamic指令的bootstrap方法的标准参数。 lookup参数封装了调用者的上下文,invokedNameinvokedType参数表示invokedynamic指令的名称和类型。

这取决于bootstrap 方法来分配更多语义。由于在这种情况下,这条指令的目的是产生一个 lambda 表达式实例,它将消耗捕获的值并产生一个 interface 实例。因此 invokedType 将具有反映捕获值类型的参数类型,或者对于非捕获 lambda 是无参数的,并且具有与所需的 功能接口相匹配的 return 类型 . invokedName 用于指定函数式接口的方法名,这个参数不常见,因为这里并没有真正调用它,但是由于调用的名称没有其他含义,所以这里重用了这个参数。

samMethodType 是功能接口实现方法的签名(在字节码级别),它与 instantiatedMethodType 相同,例如不涉及泛型。否则,samMethodType 将被类型擦除,而 instantiatedMethodType 包含实际类型参数,例如实施 Function<String,Integer>

  • invokedType 将具有 return 类型的 Function
  • samMethodType 将是 (Object)Object
  • instantiatedMethodType 将是 (String)Integer

请注意,对于您的特定情况,类型基本上是正确的,但是由于您想在提供的 process 实例上调用目标方法,因此您必须将它绑定到 lambda 实例(您没有甚至尝试)。不幸的是,你没有说清楚你在问题中遇到了什么样的实际问题(即你得到了 LambdaConversionException),所以我之前没有注意到这个问题。

如上所述,invokedType 必须包含要捕获为参数类型的值的类型。然后,您必须将实际的 process 实例传递给 invoke 调用。顾名思义,invokedType必须匹配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);
      }
}