【问题标题】:Types in a LambdaMetaFactoryLambdaMetaFactory 中的类型
【发布时间】:2016-09-22 13:33:09
【问题描述】:

当我调用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 的文档中无法完全理解。我无法确定正确的参数:

  • MethodHandles.Lookup 调用者——很简单
  • String invokedName -- 我很确定
  • MethodType invokedType -- 这是什么?
  • MethodType samMethodType -- err... 这里不确定
  • MethodHandle implMethod -- 没关系
  • MethodType instantiatedMethodType -- 又是什么? 第二次?

所以归结为:

  • MethodType 调用类型
  • 方法类型 samMethodType
  • MethodType 实例化方法类型

我的代码是这样的:

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

}

【问题讨论】:

  • 你到底想做什么? LMF 在很大程度上是编译器编写者的工具——将其视为“创建 lambda 字节码”。
  • @BrianGoetz 我有一种状态机,其中每个状态都由 java 代码编程,边缘当前通过方法名称和反射进行编程,即。 void step_3() { if(x) outgoing("step_4"); }。当然,您可以在这里犯错误,您只会在运行时注意到。所以我认为使用 methd refs 会更安全,即。 void step_3() { if(x) outgoing(::step_4); }。然后在方法引用而不是字符串上工作。为了支持遗留代码,我需要从步骤号int i(例如5)到方法参考step_i,例如。 step_5.
  • 您为什么不直接使用 AProcess::step_1 作为测试源中的方法引用,然后完成?我认为你把这件事弄得太复杂了......
  • @BrianGoetz 机器的状态保持不变。目前,持久性是通过将i 存储起来。然后我稍后将i 取回我必须从i 获取到方法引用才能执行它。我不认为我使事情过于复杂,但也许我遗漏了一些东西。引擎本身只能在方法引用上工作,当然,这是我的计划。但是为了从数据库中的状态提供状态机引擎,我必须从该状态生成方法引用。如果我可以将方法引用或整个闭包存储在数据库中,那将是我的一些东西。 ;-)(快乐的 python-stackless 程序员...)

标签: java reflection lambda java-8 method-reference


【解决方案1】:

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

这取决于引导方法来分配更多语义。由于在此上下文中,此指令的目的是生成 lambda 表达式实例,因此它将使用捕获的值并生成 interface 实例。因此invokedType 将具有反映捕获值类型的参数类型,或者对于非捕获 lambda 而言是无参数的,并且具有与所需的功能接口匹配的返回类型。 invokedName用于指定函数接口的方法名,不寻常的是这里并没有实际调用,但由于调用的名称没有其他意义,所以这里重用了这个参数。

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

  • invokedType 的返回类型为 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);
      }
}

【讨论】:

  • 好的,这让我更进一步。在我的情况下invokedType 将是Function<Boolean>(因为我调用Boolean step() 生成由MethodType.methodType(Function.class);samMethodType...错误...MethodType.methodType(Object.class) 因为Function<Boolean> 类型擦除??和@987654355 @ 将由MethodType.methodType(...) 生成...?实际上,在我的示例中,我看不到(String)Integer 的来源。另外,如何使用methodType() 调用生成(A)B?是吗,如你所说再次samMethodType,即MethodType.methodType(Object.class)?
  • 不,您生成了Step 的实例,因此您的invokedType()Step,您已经通过MethodType.methodType(Step.class) 正确创建了它。由于这个接口不是通用的,samMethodTypeinstantiatedMethodType 都是()Boolean,您已经通过MethodType.methodType(Boolean.class) 正确创建了它们。无需更改任何内容。
  • 不要对(ParameterTypes)ReturnType 语法感到困惑。使用MethodType.methodType(ReturnType [,ParameterTypes]) 时必须更改顺序,这对于没有参数的签名并不重要。或者您使用遵循(ParameterTypes)ReturnType 语法的MethodType.fromMethodDescriptorString("()Ljava/lang/Boolean;"),但是它不会使代码更具可读性。
  • (2) 我明白了。事实上,我并不是说我想要一个 Lambda,因为我不知道。但既然你这么说,那是显而易见的。现在我理解了invoke-call,很明显,我需要通过this。我想我现在获得了很多理解。
猜你喜欢
  • 2019-03-19
  • 2021-09-18
  • 2015-01-02
  • 1970-01-01
  • 1970-01-01
  • 2021-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多