使用LambdaMetafactory转换Record构造为Function时遇MethodHandle无法破解问题
解决LambdaMetafactory转换Record构造器为Function<Object[], T>的错误问题
问题描述
希望通过LambdaMetafactory将Record的构造器转换为Function<Object[], T>(T为泛型类型),测试代码如下:
public record R( String a, String b ) { } private static void testRecord() throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle constructor = lookup.findConstructor(R.class, MethodType.methodType(void.class, String.class, String.class)); constructor = constructor.asSpreader(Object[].class, 2); R r = (R) constructor.invokeExact(new Object[]{"a", "b"}); System.out.println(r.a()); System.out.println(r.b()); MethodType methodType = constructor.type(); CallSite callSite = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class), methodType.erase(), constructor, methodType); Function<Object[], R> f = (Function<Object[], R>) callSite.getTarget().invokeExact(); R apply = f.apply(new Object[]{"a", "b"}); System.out.println(apply.a()); System.out.println(apply.b()); }
使用constructor.invokeExact()可成功实例化Record,但调用LambdaMetafactory.metafactory()生成CallSite时抛出错误:
Exception in thread "main" java.lang.invoke.LambdaConversionException: MethodHandle(Object[])R is not direct or cannot be cracked at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.<init>(AbstractValidatingLambdaMetafactory.java:143) at java.base/java.lang.invoke.InnerClassLambdaMetafactory.<init>(InnerClassLambdaMetafactory.java:168) at java.base/java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:336)
问题原因
constructor.asSpreader(Object[].class, 2)返回的是经过包装的MethodHandle,并非直接指向构造器的原生句柄。而LambdaMetafactory要求目标方法句柄必须是direct method handle(即直接指向构造器、普通方法或字段的句柄,不能是经过asSpreader、asCollector等转换后的包装句柄),否则会触发转换异常。
解决方案
方案1:利用LambdaMetafactory的参数自动适配能力(推荐)
直接使用原始构造器MethodHandle,通过调整LambdaMetafactory的参数,让它自动将Object[]拆分为构造器所需的多参数:
private static void testRecordFixed() throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); // 获取原始构造器方法句柄,不做asSpreader转换 MethodHandle constructor = lookup.findConstructor(R.class, MethodType.methodType(void.class, String.class, String.class)); // Lambda工厂方法类型:无参数,返回Function<Object[], R> MethodType factoryType = MethodType.methodType(Function.class); // Function接口apply方法的擦除类型:参数Object[],返回Object MethodType interfaceMethodType = MethodType.methodType(Object.class, Object[].class); // 构造器方法的擦除类型:两个Object参数,返回Object MethodType targetMethodType = constructor.type().erase(); CallSite callSite = LambdaMetafactory.metafactory( lookup, "apply", factoryType, interfaceMethodType, constructor, targetMethodType ); Function<Object[], R> f = (Function<Object[], R>) callSite.getTarget().invokeExact(); R apply = f.apply(new Object[]{"a", "b"}); System.out.println(apply.a()); System.out.println(apply.b()); }
原理:LambdaMetafactory会自动生成代码拆解Object[]参数,匹配构造器的参数列表,无需额外包装,简洁高效。
方案2:将包装后的MethodHandle封装为静态方法
如果必须保留asSpreader处理的句柄,可以将其封装到静态方法中,再通过静态方法的direct句柄生成Lambda:
public record R(String a, String b) {} private static final MethodHandle SPREAD_CONSTRUCTOR; static { try { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle constructor = lookup.findConstructor(R.class, MethodType.methodType(void.class, String.class, String.class)); SPREAD_CONSTRUCTOR = constructor.asSpreader(Object[].class, 2); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } } // 封装静态方法,调用包装后的构造器句柄 private static R createFromArray(Object[] args) throws Throwable { return (R) SPREAD_CONSTRUCTOR.invokeExact(args); } private static void testRecordFixedWithStatic() throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); // 获取静态方法的direct类型句柄 MethodHandle createMethod = lookup.findStatic(YourClass.class, "createFromArray", MethodType.methodType(R.class, Object[].class)); CallSite callSite = LambdaMetafactory.metafactory( lookup, "apply", MethodType.methodType(Function.class), createMethod.type().erase(), createMethod, createMethod.type() ); Function<Object[], R> f = (Function<Object[], R>) callSite.getTarget().invokeExact(); R apply = f.apply(new Object[]{"a", "b"}); System.out.println(apply.a()); System.out.println(apply.b()); }
原理:静态方法的MethodHandle属于direct类型,符合LambdaMetafactory的要求,静态方法内部负责处理包装句柄的调用逻辑。
内容的提问来源于stack exchange,提问作者benrush
相关产品推荐
相关产品推荐

