为何自定义函数式接口下LambdaMetafactory失效,Function却正常?
下面这段Java代码使用LambdaMetafactory动态生成函数式接口实例,运行时runFunction和runBuilder2能正常执行,但启用runBuilder1会抛出异常:
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"); } }
抛出的异常信息:
java.lang.AbstractMethodError: Receiver class Testcase$$Lambda$233/0x0000000800d21d88 does not define or inherit an implementation of the resolved method 'abstract java.lang.Object apply(java.lang.String)' of interface MyBuilder1.
问题原因解析
核心问题出在LambdaMetafactory.metafactory方法的**第四个参数(samMethodType,即函数式接口方法的擦除类型)**与目标方法句柄的匹配逻辑上,逐个拆解三个案例:
Function<String, IllegalArgumentException> 案例
Function的泛型方法签名擦除后是Object apply(Object),我们传入的mh.type().erase()是构造器擦除后的void(Object)。LambdaMetafactory会自动处理类型适配:将传入的String向上转型为Object传给构造器,再把构造器返回的实例向上转型为Object,最后强制转回Function要求的IllegalArgumentException,完全符合JVM类型规则,因此可以正常运行。MyBuilder2
案例 MyBuilder2的方法签名擦除后同样是Object apply(Object),和Function的场景一致,String作为Object的子类可以合法向上转型适配构造器参数,所以执行正常。MyBuilder1
案例 MyBuilder1的方法签名擦除后是Object apply(String),但我们传入的samMethodType是构造器的擦除类型void(Object)。此时LambdaMetafactory生成的Lambda实例,实际实现的是擦除后的Object apply(Object)方法,而非MyBuilder1要求的Object apply(String)。当调用apply("test")时,JVM找不到对应方法的实现,就抛出了AbstractMethodError。
修复方案
给MyBuilder1对应的metafactory调用传入正确的samMethodType——也就是MyBuilder1接口方法擦除后的类型:
MethodHandle myBuilderConstructor1 = LambdaMetafactory.metafactory( lookup, "apply", MethodType.methodType(MyBuilder1.class), // 替换为MyBuilder1接口方法的擦除类型 MethodType.methodType(Object.class, String.class), mh, mh.type() ).getTarget();
修改后生成的Lambda会正确实现MyBuilder1的apply(String)方法,即可正常执行。
内容的提问来源于stack exchange,提问作者Gili

