2015-08-25 4 views
11

Sto cercando un modo per richiamare più metodi di argomento ma utilizzando un costrutto lambda. Nella documentazione si dice che lambda è utilizzabile solo se può essere mappato su un'interfaccia funzionale.Java 8: Lambda con argomenti variabili

voglio fare qualcosa di simile:

test((arg0, arg1) -> me.call(arg0, arg1)); 
test((arg0, arg1, arg2) -> me.call(arg0, arg1, arg2)); 
... 

C'è un modo che si può fare questo con eleganza senza definire 10 interfacce, una per ogni argomento contare?

Aggiorna

uso più interfacce estendono da un'interfaccia non metodo e sovraccaricare il metodo.

Esempio per due argomenti:

interface Invoker {} 
interface Invoker2 extends Invoker { void invoke(Object arg0, Object arg1);} 
void test(Invoker2 invoker, Object ... arguments) { 
    test((Invoker)invoker, Object ... arguments); 
} 

void test(Invoker invoker, Object ... arguments) { 
    //Use Reflection or whatever to access the provided invoker 
} 

Spero in una possibilità di sostituire le interfacce 10 e le 10 invoker metodi di overload con un'unica soluzione.

Ho un caso d'uso ragionevole e per favore non fare domande del tipo "Perché dovresti fare una cosa del genere?" e 'Qual è il problema che stai cercando di risolvere?' o qualcosa del genere. Sappi solo che ci ho pensato e questo è un problema legittimo che cerco di risolvere.

Siamo spiacenti di aggiungere confusione chiamandolo invoker ma è in realtà quello che viene chiamato nel mio caso d'uso corrente (collaudo contratti di costruzione).

Fondamentalmente, come indicato sopra, pensare a un metodo che funziona con un diverso numero di attributi all'interno dello lambda.

+6

Risposta breve: no. Risposta lunga: nooooooooo. (Ma poi, cosa faresti comunque con una cosa del genere?) –

+0

Tutti i tuoi argomenti sono dello stesso tipo? In caso affermativo hai esaminato l'utilizzo di varargs? Qual è un esempio concreto con il contesto di ciò che stai cercando di fare? – user4987274

+0

Louis scusa, faccio un aggiornamento per te (update2). –

risposta

1

La soluzione finale che attualmente utilizzo è definire una gerarchia di interfacce (come indicato nella domanda) e utilizzare metodi predefiniti per evitare errori. pseudo codice simile a questo:

interface VarArgsRunnable { 
    default void run(Object ... arguments) { 
      throw new UnsupportedOperationException("not possible"); 
    } 
    default int getNumberOfArguments() { 
      throw new UnsupportedOperationException("unknown"); 
    } 
} 

e un'interfaccia per quattro argomenti per esempio:

@FunctionalInterface 
interface VarArgsRunnable4 extends VarArgsRunnable { 
    @Override 
    default void run(Object ... arguments) { 
      assert(arguments.length == 4); 
      run(arguments[0], arguments[1], arguments[2], arguments[3]); 
    } 

    void run(Object arg0, Object arg1, Object arg2, Object arg3, Object arg4); 

    @Override 
    default int getNumberOfArguments() { 
      return 4; 
    } 
} 

aver definito 11 interfacce da VarArgsRunnable0 a VarArgsRunnable10 il sovraccarico di un metodo diventa abbastanza facile.

public void myMethod(VarArgsRunnable runnable, Object ... arguments) { 
    runnable.run(arguments); 
} 

Dal momento che Java non può comporre un Lambda trovando l'interfaccia funzionale esteso corretto di VarArgsRunnable usando qualcosa come instance.myMethod((index, value) -> doSomething(to(index), to(value)), 10, "value") una necessità di sovraccaricare il metodo che utilizza l'interfaccia corretta.

public void myMethod(VarArgsRunnable2 runnable, Object arg0, Object arg1) { 
    myMethod((VarArgsRunnable)runnable, combine(arg0, arg1)); 
} 

private static Object [] combine(Object ... values) { 
    return values; 
} 

Poiché questo richiede di lanciare oggetto per qualsiasi tipo appropriato utilizzando to(...) uno può andare per la parametrizzazione usando Generics per evitare questo utilizzo.

Il metodo to corrisponde a questo: T statico pubblico (valore oggetto) { valore di ritorno (T); // Sopprimere questo avvertimento }

L'esempio è zoppo, ma lo uso per chiamare un metodo con più argomenti di essere una permutazione di tutti i potenziali combinazioni (per scopi di test) come:

run((index, value) -> doTheTestSequence(index, value), values(10, 11, 12), values("A", "B", "C")); 

Così questo piccolo linea esegue 6 invocazioni. Quindi vedi che questo è un aiuto pulito che è in grado di testare più cose in una singola riga invece di definirne molte altre o usare più metodi in TestNG e qualsiasi altra cosa ...

PS: Non avendo bisogno di usare i riflessi è piuttosto una buona cosa, dal momento che non può fallire ed è abbastanza risparmiare argomento argomento saggio.

+0

Questa è una soluzione pratica, ma allo stesso tempo parla di come Java può essere distorto solo per fare questo genere di cose. Mi chiedo se c'è un modo elegante. – matanster

9

In Java è necessario utilizzare un array come questo.

test((Object[] args) -> me.call(args)); 

Se call prende un array args questo lavoro. Altrimenti puoi usare il reflection per effettuare la chiamata.

+0

Ho aggiunto un aggiornamento per darti il ​​mio stato attuale. The Object [] args era esattamente il muro in cui mi sono imbattuto e ho lavorato in giro. Ora voglio ridurre la soluzione alternativa a quanto consentito da Java 8. –

+1

@MartinKersten Java ha un compilatore statico, questo significa che può compilare solo per chiamate di metodo note al momento della compilazione. Se non si conosce il tipo fino al runtime, è necessario utilizzare la reflection. –

+0

Voglio avere argomenti variabili con lambda. So esattamente quale riflessione è, ma nel tuo esempio devi fornire un nuovo oggetto [] {arg0, arg1} per ogni chiamata che è prolissa e voglio evitarlo. Quindi dai la tua soluzione con {arg0, arg1} è il vero affare che sto cercando. La riflessione non aiuta con questo ma è parte della soluzione :-). –

0

Credo che il seguente codice dovrebbe essere adattabile a ciò che si vuole:

public class Main { 
    interface Invoker { 
     void invoke(Object ... args); 
    } 

    public static void main(String[] strs) { 
     Invoker printer = new Invoker() { 
      public void invoke(Object ... args){ 
       for (Object arg: args) { 
        System.out.println(arg); 
       } 
      } 
     }; 

     printer.invoke("I", "am", "printing"); 
     invokeInvoker(printer, "Also", "printing"); 
     applyWithStillAndPrinting(printer); 
     applyWithStillAndPrinting((Object ... args) -> System.out.println("Not done")); 
     applyWithStillAndPrinting(printer::invoke); 
    } 

    public static void invokeInvoker(Invoker invoker, Object ... args) { 
     invoker.invoke(args); 
    } 

    public static void applyWithStillAndPrinting(Invoker invoker) { 
     invoker.invoke("Still", "Printing"); 
    } 
} 

Si noti che non c'è bisogno di creare e passare un lambda per me.call perché si dispone già di un riferimento a tale metodo. È possibile chiamare test(me::call) proprio come chiamo applyWithStillAndPrinting(printer::invoke).

+0

Grazie per il codice ma stavo cercando di usare lambda. L'esempio di invoker è proprio quello che ho chiamato dal momento che si tratta di invocare metodi di test che generano scenari di test. –

+0

Nota che lambdas è solo un costrutto di call-site, vedi http://stackoverflow.com/questions/13604703/how-do-i-define-a-method-which-takes-a-lambda-as-a-parameter -in-java-8. L'invocatore sopra * è * un lambda. la stampante esiste solo per comodità a meno che la domanda che stai facendo sia come definire un lambda. – user4987274

+0

L'invocatore non è una lambda. Un lambda è qualcosa nella forma di (arg0, arg1) -> doSomething() ;. Una lambda viene tradotta (mappata) in un'interfaccia funzionale, di solito pensata per essere annotata con @FunctionalInterface ma diventa facoltativa, quindi ogni interfaccia può essere utilizzata se possibile. Quindi prova il tuo invoker come argomento per un metodo e usa (arg0, arg1) -> System.println ("message" + arg0 + "," + arg1) ;. Fai lo stesso con (arg0, arg1, arg2) -> System.println ("message" + arg0 + "," + arg1 + "," + arg2). –