java - 创建 Lambda 函数实例

标签 java class lambda jvm java-bytecode-asm

我试图理解 lambda 表达式并遇到以下问题。我知道 lambda 表达式被 javacindy 的基 native 制编译成 invokedynamic 指令。

我有类加载器:

public class MyClassLoader extends ClassLoader{
    public Class<?> defineClass(byte[] classData){
        Class<?> cls = defineClass(null, classData, 0, classData.length);
        resolveClass(cls); 
        return cls; //should be ok, resolved before returning
    }
}

现在我想用 ASM 动态创建一个手工制作的 Class 并在 LambdaMetafactory 中使用它来创建我的功能接口(interface)的实例。在这里:

@FunctionalInterface
public interface Fnct {
    Object apply(String str);
}

这是我的完整申请:

public static void main(String[] args) throws Throwable {
    System.out.println(
          generateClassWithStaticMethod().getMethod("apply", String.class)
                 .invoke(null, "test")   
    ); //prints 3 as expected

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh = lookup.findStatic(generateClassWithStaticMethod(), "apply", MethodType.methodType(Object.class, String.class));
    Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
            mh.type(), mh, mh.type()).getTarget().invokeExact();

    f.apply("test"); //throws java.lang.NoClassDefFoundError: MyTestClass
}

public static Class<?> generateClassWithStaticMethod(){
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

    classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "MyTestClass", null, getInternalName(Object.class), null);

    MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC + ACC_STATIC, "apply", "(Ljava/lang/String;)Ljava/lang/Object;",null, null);
    mv.visitInsn(ICONST_3);
    mv.visitMethodInsn(INVOKESTATIC, getInternalName(Integer.class), "valueOf", "(I)Ljava/lang/Integer;", false);
    mv.visitInsn(ARETURN);
    mv.visitMaxs(0, 0);
    mv.visitEnd();

    return new MyClassLoader().defineClass(classWriter. toByteArray());
}

因此反射方法调用成功,但使用 LambdaMetafactory 创建和调用实例失败并返回 NoClassDefFoundError。我尝试使用静态方法在 Java 中创建一个类,它成功了:

public class Fffnct {
    public static Object apply(String str){
        return 3;
    }
}

我发现类文件的唯一区别是 javac 生成:

LineNumberTable:
    line 5: 0

我尝试自己将其添加为 mv.visitLineNumber(5, new Label()); 但不幸的是,它没有用。

我动态生成的类有什么问题?

最佳答案

关键部分是 MethodHandles.Lookup 实例,它定义了 lambda 将存在的上下文。由于您是通过 MethodHandles.lookup() 在您的main 方法,它封装了一个上下文,在这个上下文中,新类加载器定义的类是不可见的。您可以通过 in(Class) 更改上下文但这会改变访问模式并导致 LambdaMetaFactory 拒绝查找对象。在 Java 8 中,没有标准的方法来创建具有对另一个类的私有(private)访问权限的查找对象。

仅出于演示目的,我们可以使用具有访问覆盖的反射来生成适当的查找对象,以表明它随后会起作用:

Class<?> generated = generateClassWithStaticMethod();
MethodHandles.Lookup lookup = MethodHandles.lookup().in(generated);
Field modes = MethodHandles.Lookup.class.getDeclaredField("allowedModes");
modes.setAccessible(true);
modes.set(lookup, -1);
MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Object.class, String.class));
Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
        mh.type(), mh, mh.type()).getTarget().invokeExact();
Object result = f.apply("test");
System.out.println("result: "+result);

但是,众所周知,不鼓励使用覆盖访问的反射,它会在 Java 9 中生成警告,并可能在未来的版本中中断,其他 JRE 可能甚至没有此字段。

另一方面,Java 9 引入了一种获取查找对象的新方法,如果当前模块依赖项不禁止的话:

Class<?> generated = generateClassWithStaticMethod();
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup = MethodHandles.privateLookupIn(generated, lookup);// Java 9
MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Object.class, String.class));
Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
        mh.type(), mh, mh.type()).getTarget().invokeExact();
Object result = f.apply("test");
System.out.println("result: "+result);

Java 9 引入的另一个选项是在您自己的包中生成一个类,而不是新的类加载器。然后,您自己的类查找上下文可以访问它:

public static void main(String[] args) throws Throwable {
    byte[] code = generateClassWithStaticMethod();
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    Class<?> generated = lookup.defineClass(code);// Java 9
    System.out.println("generated "+generated);
    MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Object.class, String.class));
    Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
            mh.type(), mh, mh.type()).getTarget().invokeExact();
    Object result = f.apply("test");
    System.out.println("result: "+result);
}

public static byte[] generateClassWithStaticMethod() {
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "MyTestClass", null, "java/lang/Object", null);
    MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC + ACC_STATIC, "apply", "(Ljava/lang/String;)Ljava/lang/Object;",null, null);
    mv.visitInsn(ICONST_3);
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
    mv.visitInsn(ARETURN);
    mv.visitMaxs(0, 0);
    mv.visitEnd();
    byte[] byteArray = classWriter.toByteArray();
    return byteArray;
}

如果您继续使用自定义类加载器,您可以利用这样一个事实,即无论如何您都在进行代码生成。因此,您可以在生成的类中生成调用 MethodHandles.lookup() 的方法并返回它。然后,通过反射调用它,您将获得一个表示生成类上下文的查找对象。另一方面,您也可以将生成 lambda 实例的指令直接插入生成的类本身:

public static void main(String[] args) throws Throwable {
    String staticMethodName = "apply";
    MethodType staticMethodType = MethodType.methodType(Object.class, String.class);
    Class<?> generated = generateClassWithStaticMethod("TestClass", Object.class,
        staticMethodName, staticMethodType, Fnct.class, "apply", staticMethodType);
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    System.out.println("generated "+generated);
    MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Fnct.class));
    Fnct f =  (Fnct)mh.invokeExact();
    Object result = f.apply("test");
    System.out.println("result: "+result);
}

public static Class<?> generateClassWithStaticMethod(String clName, Class<?> superClass,
    String methodName, MethodType methodType, Class<?> funcInterface, String funcName, MethodType funcType) {

    Class<?> boxedInt = Integer.class;
    ClassWriter classWriter = new ClassWriter(0);
    classWriter.visit(V1_8, ACC_PUBLIC|ACC_SUPER, clName, null, getInternalName(superClass), null);
    MethodVisitor mv = classWriter.visitMethod(
         ACC_PUBLIC|ACC_STATIC, methodName, methodType.toMethodDescriptorString(), null, null);
    mv.visitInsn(ICONST_3);
    mv.visitMethodInsn(INVOKESTATIC, getInternalName(boxedInt), "valueOf",
        MethodType.methodType(boxedInt, int.class).toMethodDescriptorString(), false);
    mv.visitInsn(ARETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();
    String noArgReturnsFunc = MethodType.methodType(funcInterface).toMethodDescriptorString();
    mv = classWriter.visitMethod(ACC_PUBLIC|ACC_STATIC, methodName, noArgReturnsFunc, null, null);
    Type funcTypeASM = Type.getMethodType(funcType.toMethodDescriptorString());
    mv.visitInvokeDynamicInsn(funcName, noArgReturnsFunc, new Handle(H_INVOKESTATIC,
        getInternalName(LambdaMetafactory.class), "metafactory", MethodType.methodType(CallSite.class,
            MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class,
            MethodHandle.class, MethodType.class).toMethodDescriptorString()), funcTypeASM,
            new Handle(H_INVOKESTATIC, clName, methodName, methodType.toMethodDescriptorString()),
            funcTypeASM
        );
    mv.visitInsn(ARETURN);
    mv.visitMaxs(1, 0);
    mv.visitEnd();
    return new MyClassLoader().defineClass(classWriter.toByteArray());
}

这会生成第二个具有相同名称但没有参数的静态方法,返回功能接口(interface)的实例,生成完全像对第一个静态方法的方法引用,使用单个 invokedynamic 指令。当然,这只是为了演示逻辑,因为直接在其函数方法中生成实现接口(interface)执行操作的类很容易,而不需要元工厂生成委托(delegate)类。

关于java - 创建 Lambda 函数实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49449915/

相关文章:

java - Intellij 运行 Spring Boot 应用程序时没有运行按钮

c# - 从多个类继承的解决方法?

C++ 将内部枚举用于类模板

Java 8 Streams.reduce() 与组合器

c# - 可等待的 lambda

c# - 你如何像 C# 一样使用 C++ lambda

java - Hibernate 在更新唯一键的字段部分时抛出唯一约束违反异常

Java 数组移位引用

java - 共享 Java 对象流

ios - Objective-C 类扩展