2014-11-06 65 views
16

我試圖明確使用LambdaMetafactory.metafactory,我不明白爲什麼它只與Runnable功能接口一起使用。例如,該代碼所做的預期(它打印的「Hello World」):當我嘗試使用不同的功能界面,如供應商出現明確使用LambdaMetafactory

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) 

不應該兩個代碼段的工作方式類似,這是第二個代碼段中的問題嗎?

此外下面的代碼,這應該是等效,效果很好:

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

也許問題出在您作爲第二個參數傳遞的方法名「run」。 Runnable有一個「運行」方法。供應商沒有。 – Eran 2014-11-06 09:31:24

+0

這是一個錯誤(Runnable案件只適用於「運行」),但也得到第二個片段給出了該錯誤。 – andrebask 2014-11-06 09:39:38

回答

14

Runnable和Supplier之間的區別在於Supplier使用泛型類型。

在運行時供應商沒有String get()方法,它具有Object get()。但是你實現的方法返回一個String。你需要區分這兩種類型。就像這樣:

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

如果打印方法包含參數,這樣做會工作嗎? – 2016-02-29 22:25:40

+1

如果打印方法需要參數,則不能用於實現「可運行」或「供應商」界面。 – 2016-03-03 08:39:40

+0

他們有什麼可以使用的?你會推薦哪個?在我的例子中,我試圖調用一個返回一個布爾值的非靜態函數,並接受一個或多個字符串作爲參數。 – 2016-03-09 19:30:13

0

這是一個更容易的另一個例子就明白了變量名:

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方法的方法引用。對於有關閉的lambdas,您會撥打factory.create(capturedValue1, ..., capturedValueN)這樣的工廠,因此您必須相應地修改callSiteType