2014-11-06 11 views
16

Sto tentando di utilizzare in modo esplicito LambdaMetafactory.metafactory, non riesco a capire perché funzioni solo con l'interfaccia funzionale Runnable. Per esempio questo codice fa quello che ci si aspetta (esso stampa "ciao mondo"):Uso esplicito di 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"); 
    }  
} 

Il problema si pone quando si tenta di utilizzare un'interfaccia funzionale differente, come fornitore. Il seguente codice non funziona:

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) 

non Qualora due frammento di codice funzionante in modo simile, che è il problema nel secondo frammento di codice?

Inoltre il codice seguente, che deve essere equivalente, funziona bene:

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

Modifica

Un'altra soluzione che evita di modificare il tipo di ritorno del metodo è quello di definire una nuova interfaccia funzionale:

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

Forse il problema è con il nome del metodo "run" si passa come secondo argomento. Runnable ha un metodo "run". Il fornitore no. – Eran

+0

Quello era un errore (il caso Runnable funziona solo con "run"), ma anche con get il secondo snippet dà quell'errore. – andrebask

risposta

14

La differenza tra eseguibile e fornitore è che il fornitore utilizza un tipo generico.

A runtime Il fornitore non ha un metodo string(), ha Object get(). Ma il metodo implementato restituisce una stringa. È necessario distinguere tra questi 2 tipi. Come questo:

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

Funzionerebbe se il metodo di stampa contenesse un argomento? –

+1

Se il metodo di stampa richiede un argomento, non può essere utilizzato per implementare l'interfaccia "Runnable" o "Supplier". –

+0

È il loro qualcosa che può essere utilizzato? Quale raccomanderesti? Nella mia istanza, sto provando a richiamare una funzione non statica che restituisce un valore booleano e prende una o più stringhe come parametri. –

0

Questo è un altro esempio con un più facile da capire i nomi delle variabili:

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

Perché LambdaMetafactory crea una classe factory sintetico che poi viene utilizzato per creare l'interfaccia di destinazione, callSiteType ha un tipo di questo metodo di fabbrica create(). Questo metodo create() viene chiamato implicitamente da invokedynamic - LambdaMetafactory restituisce un CallSite con un riferimento al metodo di creazione. Per lambda con chiusure chiamerai la fabbrica come factory.create(capturedValue1, ..., capturedValueN) e quindi devi modificare callSiteType di conseguenza.