为什么 LambdaMetafactory 在使用自定义函数接口时会失败(但 Function 工作正常)?

Gil*_*ili 6 java methodhandle lambda-metafactory

鉴于:

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

class Testcase
{
    @FunctionalInterface
    public interface MyBuilder1<R>
    {
        R apply(String message);
    }

    @FunctionalInterface
    public interface MyBuilder2<R>
    {
        R apply(Object message);
    }

    public static void main(String[] args) throws Throwable
    {
        Class<?> clazz = IllegalArgumentException.class;

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle mh = lookup.findConstructor(clazz, MethodType.methodType(void.class, String.class));
        MethodHandle myFunctionConstructor = LambdaMetafactory.metafactory(
            lookup,
            "apply",
            MethodType.methodType(Function.class),
            mh.type().erase(),
            mh,
            mh.type()
        ).getTarget();

        MethodHandle myBuilderConstructor1 = LambdaMetafactory.metafactory(
            lookup,
            "apply",
            MethodType.methodType(MyBuilder1.class),
            mh.type().erase(),
            mh,
            mh.type()
        ).getTarget();

        MethodHandle myBuilderConstructor2 = LambdaMetafactory.metafactory(
            lookup,
            "apply",
            MethodType.methodType(MyBuilder2.class),
            mh.type().erase(),
            mh,
            mh.type()
        ).getTarget();

        @SuppressWarnings("unchecked")
        Function<String, IllegalArgumentException> functionFactory =
            (Function<String, IllegalArgumentException>) myFunctionConstructor.invokeExact();

        @SuppressWarnings("unchecked")
        MyBuilder1<IllegalArgumentException> myBuilder1Factory =
            (MyBuilder1<IllegalArgumentException>) myBuilderConstructor1.invokeExact();

        @SuppressWarnings("unchecked")
        MyBuilder2<IllegalArgumentException> myBuilder2Factory =
            (MyBuilder2<IllegalArgumentException>) myBuilderConstructor2.invokeExact();

        IllegalArgumentException runFunction = functionFactory.apply("test");
//      IllegalArgumentException runBuilder1 = myBuilder1Factory.apply("test");
        IllegalArgumentException runBuilder2 = myBuilder2Factory.apply("test");

    }
}
Run Code Online (Sandbox Code Playgroud)

为什么 dorunFunctionrunBuilder2work whilerunBuilder1会抛出以下异常?

java.lang.AbstractMethodError:接收器类 Testcase$$Lambda$233/0x0000000800d21d88 未定义或继承接口 MyBuilder1 的已解析方法“抽象 java.lang.Object apply(java.lang.String)”的实现。

鉴于IllegalArgumentException构造函数采用String参数,而不是Object,JVM 是否应该接受runBuilder1并抱怨其他两个参数类型?

Hol*_*ger 9

MyBuilder1<R>有一个函数方法

R apply(String message);
Run Code Online (Sandbox Code Playgroud)

其擦除类型是

Object apply(String message);
Run Code Online (Sandbox Code Playgroud)

换句话说,与Functionor不同MyBuilder2,擦除的参数类型是String, 而不是Objecterase()的方法MethodType只是将所有引用类型替换为,这对于和 来说Object很方便,但不再适合了。对于非平凡类型,没有类似的简单方法。您必须包含专门针对您的情况的类型转换代码(除非您想通过反射查找接口方法)。FunctionMyBuilder2MyBuilder1

例如,我们可以将返回类型更改为Object并保留参数类型:

class Testcase
{
    @FunctionalInterface
    public interface MyBuilder1<R>
    {
        R apply(String message);
    }

    public static void main(String[] args) throws Throwable
    {
        Class<?> clazz = IllegalArgumentException.class;

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle mh = lookup.findConstructor(clazz,
            MethodType.methodType(void.class, String.class));

        MethodHandle myBuilderConstructor1 = LambdaMetafactory.metafactory(
            lookup,
            "apply",
            MethodType.methodType(MyBuilder1.class),
            mh.type().changeReturnType(Object.class), // instead of erase()
            mh,
            mh.type()
        ).getTarget();

        @SuppressWarnings("unchecked")
        MyBuilder1<IllegalArgumentException> myBuilder1Factory =
            (MyBuilder1<IllegalArgumentException>) myBuilderConstructor1.invokeExact();

        IllegalArgumentException runBuilder1 = myBuilder1Factory.apply("test");

        runBuilder1.printStackTrace();
    }
Run Code Online (Sandbox Code Playgroud)

关于您的最后一个问题,擦除的类型是要实现的类型,而最后一个参数确定metafactory预期的类型,即从通用接口类型派生。必要时,生成的代码可能会进行从已擦除类型到此类型的类型转换。由于此类型在所有情况下都与构造函数签名匹配,因此所有变体都可以调用构造函数。

  • 该错误根本不是来自“LambdaMetafactory”。它根据你提供的信息创建了一个类,没有检查。因此它创建了一个具有原始类型“Object apply(Object)”方法的类。然后,您尝试从“MyBuilder1”接口调用该方法,该接口不存在,因此,得到“AbstractMethodError”报告,方法“Object apply(String)”不存在,尽管它应该存在,就像类一样实现接口。 (3认同)