显式使用 LambdaMetafactory

2022-09-01 14:35:39

我试图明确使用LambdaMetafactory.metafactory,我不明白为什么它只适用于Runnable功能接口。例如,此代码执行预期的操作(它打印“hello world”):

public class MetafactoryTest {
    
    public static void main(String[] args) throws Throwable {

        MethodHandles.Lookup caller = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(void.class);
        MethodType invokedType = MethodType.methodType(Runnable.class);
        CallSite site = LambdaMetafactory.metafactory(caller, 
                                                      "run", 
                                                      invokedType, 
                                                      methodType, 
                                                      caller.findStatic(MetafactoryTest.class, "print", methodType), 
                                                      methodType);
        MethodHandle factory = site.getTarget();
        Runnable r = (Runnable) factory.invoke();
        r.run();
    }
    
    private static void print() {
        System.out.println("hello world"); 
    }    
}

当我尝试使用不同的功能接口(如 Supplier)时,问题就出现了。下面的代码不起作用:

public class MetafactoryTest {

    public static void main(String[] args) throws Throwable {

        MethodHandles.Lookup caller = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(String.class);
        MethodType invokedType = MethodType.methodType(Supplier.class);
        CallSite site = LambdaMetafactory.metafactory(caller, 
                                                      "get", 
                                                      invokedType, 
                                                      methodType, 
                                                      caller.findStatic(MetafactoryTest.class, "print", methodType), 
                                                      methodType);
        MethodHandle factory = site.getTarget();
        Supplier<String> r = (Supplier<String>) factory.invoke();
        System.out.println(r.get());        
    }
    private static String print() {
        return "hello world";
    }    
}


Exception in thread "main" java.lang.AbstractMethodError: metafactorytest.MetafactoryTest$$Lambda$1/258952499.get()Ljava/lang/Object;
    at metafactorytest.MetafactoryTest.main(MetafactoryTest.java:29)

这两个代码片段不应该以类似的方式工作吗,第二个片段有什么问题吗?

此外,以下应该是等效的代码运行良好:

public class MetafactoryTest {

    public static void main(String[] args) throws Throwable {
        Supplier<String> r = (Supplier<String>) () -> print();
        System.out.println(r.get());        
    }

    private static String print() {
        return "hello world";
    }    
}

编辑

避免更改方法返回类型的另一种解决方案是定义一个新的功能接口:

public class MetafactoryTest {

    @FunctionalInterface
    public interface Test {
        String getString();
    }
    
    public static void main(String[] args) throws Throwable {

        MethodHandles.Lookup caller = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(String.class);
        MethodType invokedType = MethodType.methodType(Test.class);
        CallSite site = LambdaMetafactory.metafactory(caller, 
                                                      "getString", 
                                                      invokedType, 
                                                      methodType, 
                                                      caller.findStatic(MetafactoryTest.class, "print", methodType), 
                                                      methodType);
        MethodHandle factory = site.getTarget();
        Test r = (Test) factory.invoke();
        System.out.println(r.getString());        
    }
    
    private static String print() {
        return "hello world";
    }  

答案 1

Runnable 和 Supplier 之间的区别在于 Supplier 使用泛型类型。

在运行时,Supplier 没有 String get() 方法,它有 Object get()。但您实现的方法返回一个字符串。您需要区分这两种类型。喜欢这个:

public class MetafactoryTest {

    public static void main(String[] args) throws Throwable {

        MethodHandles.Lookup caller = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(Object.class);
        MethodType actualMethodType = MethodType.methodType(String.class);
        MethodType invokedType = MethodType.methodType(Supplier.class);
        CallSite site = LambdaMetafactory.metafactory(caller, 
                                                      "get", 
                                                      invokedType, 
                                                      methodType, 
                                                      caller.findStatic(MetafactoryTest.class, "print", actualMethodType), 
                                                      methodType);
        MethodHandle factory = site.getTarget();
        Supplier<String> r = (Supplier<String>) factory.invoke();
        System.out.println(r.get());
    }

    private static String print() {
        return "hello world";
    }    
}

答案 2

迟到的游戏,但LambdaMetaFactory几乎让我发疯。很难看出哪个参数去哪里。制作了一个示例,通过根据其角色命名它们来显示不同类型的更明确一些。

    class Instance {
        public RetSub get(Par p) {
            System.out.println("Yes");
            return null;
        }
    }
    static class Ret {}
    static class RetSub extends Ret {}
    static class Par {}
    static class ParSub extends Par {}
    interface If {
        Ret method(ParSub p);
    }

    @Test
    public void testInstance() throws Throwable {
        Instance instance = new Instance();
        CallSite site = LambdaMetafactory.metafactory(caller,
            "method",
            MethodType.methodType(If.class, Instance.class), //
            MethodType.methodType(Ret.class, ParSub.class), //
            caller.findVirtual(Instance.class, "get", MethodType.methodType(RetSub.class, Par.class)), //
            MethodType.methodType(RetSub.class, ParSub.class));

        MethodHandle factory = site.getTarget();

        If iff = (If) factory.invoke(instance);
        iff.method(new ParSub());
    }

我认为这很难,因为我错过了参数的语义。

  • invokedName– 显然是接口中的方法名称
  • invokedType– 这是很难的。这是 返回的方法句柄的方法类型。第一个参数是我们尝试创建的接口的类型 (),第二个参数仅在我们访问实例方法时才需要,并且是实例类型 ()。CallSite.getTarget()IfInstance
  • samMethodType– 这是我们尝试实现的函数的确切方法类型。在本例中为“Ret method(ParSub p)”。
  • implMethod– 方法句柄。必须能够将我们创建的接口方法的参数和返回类型转换为此方法句柄。
  • instantiationMethodType– 运行时检查,用于验证某些参数和返回类型是否属于比接口更窄或相同的类型。

推荐