2015-06-03 12 views
7

Quando si crea un lambda manualmente usando MethodHandles.Lookup, MethodHandle s, MethodType s, ecc., Come si può implementare l'acquisizione di variabili?Lambda Metafactory Variable Capture

Per esempio, senza la cattura:

public IntSupplier foo() { 
    return this::fortyTwo; 
} 
/** 
* Would not normally be virtual, but oh well. 
*/ 
public int fortyTwo() { 
    return 42; 
} 

e la sua forma ingombranti, utilizzando roba in java.lang.invoke:

public IntSupplier foo() { 
    MethodHandles.Lookup lookup = MethodHandles.lookup(); 
    MethodType methodType = MethodType.methodType(int.class), 
       lambdaType = MethodType.methodType(IntSupplier.class); 
    MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType); 
    CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", lambdaType, methodType, methodHandle, methodType); 
    return (IntSupplier) callSite.getTarget().invokeExact(); 
} 
/** 
* Would not normally be virtual, but oh well. 
*/ 
public int fortyTwo() { 
    return 42; 
} 

restituirebbe un semplice, inutile IntSupplier che restituisce 42 quando viene richiamato, ma cosa se uno vorrebbe catturare qualcosa?

+0

Immagino che questo serva a capire come lambdas funzioni internamente, perché altrimenti restituiresti il ​​nuovo IntSupplier() {...}; 'senza magia. – immibis

+1

Quello che non capisco, è il commento "Non sarebbe normalmente virtuale". – Holger

risposta

3

Il terzo argomento al metodo bootstrap, che hai nominato lambdaType, è il invocato tipo delle associate invokedynamic istruzione (normalmente compilata dalla JVM). La semantica è definita dal metodo bootstrap e nel caso dello LambdaMetaFactory, specifica l'interfaccia funzionale come tipo di ritorno (il tipo dell'oggetto da costruire) e i valori da acquisire come tipo di parametro (il tipo di valori da consumare quando costruendo un'istanza lambda).

Quindi, al fine di catturare this, è necessario aggiungere il tipo di this al vostro tipo invocato e superare this come argomento per la chiamata invokeExact:

public class Test { 
    public static void main(String... arg) throws Throwable { 
     System.out.println(new Test().foo().getAsInt()); 
    } 
    public IntSupplier foo() throws Throwable { 
     MethodHandles.Lookup lookup = MethodHandles.lookup(); 
     MethodType methodType = MethodType.methodType(int.class), 
        invokedType = MethodType.methodType(IntSupplier.class, Test.class); 
     MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType); 
     CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", 
      invokedType, methodType, methodHandle, methodType); 
     return (IntSupplier) callSite.getTarget().invokeExact(this); 
    } 
    public int fortyTwo() { 
     return 42; 
    } 
} 

Se si desidera catturare più valori, è devi aggiungerli alla firma nel giusto ordine. Ad esempio, per acquisire un altro int valore:

public class Test { 
    public static void main(String... arg) throws Throwable { 
     System.out.println(new Test().foo(100).getAsInt()); 
    } 
    public IntSupplier foo(int capture) throws Throwable { 
     MethodHandles.Lookup lookup = MethodHandles.lookup(); 
     MethodType methodType = MethodType.methodType(int.class, int.class), 
      functionType = MethodType.methodType(int.class), 
      invokedType = MethodType.methodType(IntSupplier.class, Test.class, int.class); 
     MethodHandle methodHandle=lookup.findVirtual(getClass(),"addFortyTwo",methodType); 
     CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", 
      invokedType, functionType, methodHandle, functionType); 
     return (IntSupplier) callSite.getTarget().invokeExact(this, capture); 
    } 
    public int addFortyTwo(int valueToAdd) { 
     return 42+valueToAdd; 
    } 
} 

Il metodo di destinazione avrà una firma costituito dal tipo this, se non static, seguito da tutti i tipi di parametri. I valori di cattura verranno mappati in base ai tipi di questa firma da sinistra a destra e i restanti tipi di parametri, se presenti, contribuiscono alla firma funzionale, quindi devono corrispondere ai tipi di parametri del metodo interface.

Ciò implica che quando non ci sono valori acquisiti e il metodo di destinazione non è static, il tipo di ricevitore del metodo potrebbe essere associato al primo tipo di firma funzionale, come in ToIntFunction<String> f=String::length;.

2

Il tuo codice non verrà eseguito. Poiché il metodo fortyTwo non è statico, è necessario acquisire this utilizzando MethodType.methodType(IntSupplier.class, getClass()) come terzo argomento su metafactory e quindi passare this come argomento a invokeExact.

Ecco un esempio di catturare una stringa utilizzando un metodo statico per mantenere le cose più semplici:

public static int len(String s) { 
    return s.length(); 
} 

public IntSupplier supplyLength(String capture) throws Throwable { 
    MethodHandles.Lookup lookup = MethodHandles.lookup(); 

    CallSite callSite = LambdaMetafactory.metafactory(
      lookup, 
      "getAsInt", 
      methodType(IntSupplier.class, String.class), 
      methodType(int.class), 
      lookup.findStatic(getClass(), "len", methodType(int.class, String.class)), 
      methodType(int.class) 
    ); 

    return (IntSupplier) callSite.getTarget().invoke(capture); 
} 
+0

Avevo dimenticato che "questo" doveva essere catturato ^^; grazie per averlo ricordato –