2014-11-06 12 views
16

Próbuję jawnie użyć LambdaMetafactory.metafactory, nie mogę zrozumieć, dlaczego działa tylko z funkcjonalnym interfejsem Runnable. Na przykład ten kod robi to, co oczekuje się (drukuje „Hello World”):Jednoznaczne użycie 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"); 
    }  
} 

Problem pojawia się, gdy próbuję użyć innego interfejsu funkcjonalne, takie jak Dostawcę. Poniższy kod nie działa:

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) 

nie powinno dwa fragment kodu pracy w podobny sposób, co jest problemem w drugim fragmencie kodu?

Ponadto następujący kod, który powinien być równy, działa dobrze:

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

Edycja

Innym rozwiązaniem, które pozwala uniknąć zmienić typ metoda powrotu jest zdefiniowanie nowego interfejsu funkcjonalne:

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

Być może problem polega na tym, że nazwa metody "uruchom" przekazujesz jako drugi argument. Runnable ma metodę "run". Dostawca tego nie robi. – Eran

+0

To był błąd (The Runnable case działa tylko z "run"), ale także z tym, że drugi fragment daje ten błąd. – andrebask

Odpowiedz

14

Różnica między Runowalem a Dostawcą polega na tym, że Dostawca stosuje rodzaj ogólny.

W środowisku wykonawczym Dostawca nie ma metody String get(), ma Object get(). Ale zastosowana metoda zwraca String. Musisz rozróżnić te 2 typy. Tak:

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

Czy to działałoby, gdyby metoda drukowania zawierała argument? –

+1

Jeśli metoda drukowania wymaga argumentu, nie można jej użyć do implementacji interfejsu "Runnable" lub "Supplier". –

+0

Czy można z nich skorzystać? Które zaleciłbyś? W moim przykładzie próbuję wywołać funkcję niestatyczną, która zwraca wartość logiczną i przyjmuje jeden lub więcej ciągów jako parametry. –

0

Jest to kolejny przykład z bardziej zrozumiałe nazwy zmiennych:

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

Ponieważ LambdaMetafactory tworzy syntetyczny klasy fabrycznej, które następnie jest używany do tworzenia interfejsu docelowego callSiteType ma rodzaj tej fabryki: metoda create(). Ta metoda create() jest nazywana niejawnie przez invokedynamic - zwraca wartość CallSite, która ma odwołanie do metody metody create. Dla lambdów z zamknięciami zadzwonisz do fabryki jak factory.create(capturedValue1, ..., capturedValueN) i musisz odpowiednio zmodyfikować callSiteType.