2014-11-06 4 views
16

Ich versuche, LambdaMetafactory.metafactory explizit zu verwenden, ich kann nicht verstehen, warum es nur mit der funktionsfähigen Runnable-Schnittstelle funktioniert. Für das Beispiel tut dieser Code, was es zu erwarten ist (es druckt „Hallo Welt“):Explizite Verwendung von 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"); 
    }  
} 

Das Problem entsteht, wenn ich versuche, eine andere funktionale Schnittstelle, wie zB Lieferanten zu verwenden. Der folgende Code funktioniert nicht:

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) 

Sollte nicht die beide Code-Snippet Arbeit in einer ähnlichen Art und Weise, die das Problem in dem zweiten Code-Snippet ist?

Darüber hinaus ist der folgende Code, dass gleichwertig sein sollte, funktioniert gut:

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

bearbeitet

andere Lösung, die der Methode Rückgabetyp zu ändern vermeidet, ist eine neue funktionale Schnittstelle zu definieren:

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

Vielleicht ist das Problem mit dem Methodennamen "run" übergeben Sie als zweites Argument. Runnable hat eine "run" -Methode. Lieferant nicht. – Eran

+0

Das war ein Fehler (Der Runnable Fall funktioniert nur mit "run"), aber auch mit get das zweite Snippet gibt diesen Fehler. – andrebask

Antwort

14

Der Unterschied zwischen Runnable und Supplier ist, dass der Lieferant einen generischen Typ verwendet.

Zur Laufzeit hat Lieferant keine String get() -Methode, es hat Object get(). Die von Ihnen implementierte Methode gibt jedoch einen String zurück. Sie müssen zwischen diesen beiden Typen unterscheiden. Wie folgt aus:

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

Würde das funktionieren, wenn die Druckmethode ein Argument enthält? –

+1

Wenn die Druckmethode ein Argument benötigt, kann sie nicht zur Implementierung der Schnittstelle "Runnable" oder "Supplier" verwendet werden. –

+0

Ist ihr etwas, das benutzt werden kann? Was würdest du empfehlen? In meiner Instanz versuche ich eine nicht statische Funktion aufzurufen, die einen booleschen Wert zurückgibt und einen oder mehrere Strings als Parameter akzeptiert. –

0

Dies ist ein weiteres Beispiel mit einem einfachen Variablenname zu verstehen:

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

Da LambdaMetafactory eine synthetische Factory-Klasse erstellt, die dann verwendet werden Ziel-Interface zu erstellen, callSiteType a hat Typ dieser Fabrik create() Methode. Diese create() Methode wird implizit von invokedynamic - LambdaMetafactory aufgerufen, gibt eine CallSite zurück, die einen Methodenverweis auf die create-Methode hat. Für Lambdas mit Verschlüssen nennen Sie die Fabrik wie factory.create(capturedValue1, ..., capturedValueN) und so müssen Sie callSiteType entsprechend ändern.

Verwandte Themen