【问题标题】:Explicit use of LambdaMetafactory显式使用 LambdaMetafactory
【发布时间】:2015-01-02 17:39:15
【问题描述】:

我正在尝试显式使用 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"); 
    }    
}

当我尝试使用不同的功能接口时出现问题,例如供应商。以下代码不起作用:

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)

代码的两个sn-p不应该以类似的方式工作,这是第二个sn-p代码的问题吗?

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

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";
    }  

【问题讨论】:

  • 也许问题在于您作为第二个参数传递的方法名称“run”。 Runnable 有一个“运行”方法。供应商没有。
  • 这是一个错误(Runnable 案例仅适用于“run”),但在获取第二个 sn-p 时也会出现该错误。

标签: java metaprogramming java-8 lambda


【解决方案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";
    }    
}

【讨论】:

  • 如果 print 方法包含一个参数,这会起作用吗?
  • 如果打印方法需要一个参数,它不能用于实现“Runnable”或“Supplier”接口。
  • 他们有什么可以用的吗?你会推荐哪个?在我的实例中,我试图调用一个非静态函数,该函数返回一个布尔值并接受一个或多个字符串作为参数。
  • @Sahil Gupta:您可以绑定值,如this answer 中所示。第一个绑定值将是方法接收者,如果目标方法不是static,则其他绑定值从左到右映射到方法的参数。函数参数(如果有)映射到剩余的未绑定方法参数。
【解决方案2】:

这是另一个变量名更容易理解的例子:

public class Demo {
    public static void main(String[] args) throws Throwable {
        Consumer<String> consumer = s -> System.out.println("CONSUMED: " + s + ".");

        consumer.accept("foo");

        MethodHandles.Lookup caller = MethodHandles.lookup();

        MethodType lambdaBodyMethodType = MethodType.methodType(void.class, String.class);
        MethodHandle lambdaBody = caller.findStatic(
                Demo.class, "my$lambda$main$0", lambdaBodyMethodType);

        // Because of the type erasure we must use Object here
        // instead of String (Consumer<String> -> Consumer).
        MethodType functionalInterfaceMethodType =
                MethodType.methodType(void.class, Object.class);

        // we must return consumer, no closure -> no additional parameters
        MethodType callSiteType = MethodType.methodType(Consumer.class);

        CallSite site = LambdaMetafactory.metafactory(
                // provided by invokedynamic:
                caller, "accept", callSiteType,
                // additional bootstrap method arguments:
                functionalInterfaceMethodType,
                lambdaBody,
                lambdaBodyMethodType);

        MethodHandle factory = site.getTarget();
        Consumer<String> r = (Consumer<String>) factory.invoke();

        r.accept("foo");
        r.accept("bar");
    }

    private static void my$lambda$main$0(String s) {
        System.out.println("CONSUMED: " + s + ".");
    }
}

因为LambdaMetafactory 创建了一个合成工厂类,然后 用于创建目标接口,callSiteType 具有该工厂create() 方法的类型。此create() 方法由invokedynamic 隐式调用-LambdaMetafactory 返回一个CallSite,它具有对create 方法的方法引用。对于带有闭包的 lambda,您将调用工厂,例如 factory.create(capturedValue1, ..., capturedValueN),因此您必须相应地修改 callSiteType

【讨论】:

    【解决方案3】:

    我遇到了一种情况,我需要调用一个向它传递一些参数的函数。类似于@Sahil Gupta 问题。我设法使用 BiFunction 进行了一些调整来解决它:

    public void testFunctionWithParameter() throws Throwable {
        SimpleBean simpleBean = new SimpleBean();
    
        MethodHandles.Lookup caller = MethodHandles.lookup();
        MethodType invokedType = MethodType.methodType(BiFunction.class);
        MethodType biFunc = MethodType.methodType(String.class, String.class);
        MethodHandle target = caller.findVirtual(SimpleBean.class, "simpleFunction", biFunc);
        MethodType func = target.type();
    
    
        CallSite site = LambdaMetafactory.metafactory(
                caller,
                "apply",
                invokedType,
                func.generic(),
                target,
                MethodType.methodType(String.class, SimpleBean.class, String.class)
        );
    
        BiFunction<SimpleBean, String, String> fullFunction = (BiFunction<SimpleBean, String, String>) site.getTarget().invokeExact();
    
    
        System.out.println(fullFunction.apply(simpleBean, "FOO"));
    
    }
    
    private class SimpleBean {
        public String simpleFunction(String in) {
            return "The parameter was " + in;
        }
    }
    

    我希望它对某人有所帮助。

    【讨论】:

      【解决方案4】:

      游戏迟到了,但 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() 返回的 MethodHandle 的 MethodType。第一个参数是我们要创建的接口的类型(If),第二个参数只有在我们访问实例方法时才需要,并且是实例类型(Instance)。
      • samMethodType – 这是我们尝试实现的函数的 exact 方法类型。在这种情况下,“Ret 方法(ParSub p)”。
      • implMethod – 方法句柄。必须可以将我们创建的接口方法的参数和返回类型转换为这个方法句柄。
      • instantiationMethodType – 运行时检查以验证某些参数和返回类型属于比接口更窄或相同的类型。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多