2015-09-16 6 views
5

Se creo un'interfaccia funzionale:Come utilizzare direttamente una funzione come un tipo funzionale in Java 8

@FunctionalInterface 
public class Consumer2<T1, T2> { 
    void accept(T1 t1, T2 t2); 

    default Consumer1<T2> curry(T1 t1) { 
     return (t2) -> accept(t1, t2); 
    } 
} 

Ora, se ho una classe:

public class MyClass { 
    public void printStrings(String a, String b) { 
     System.out.println(a + ": " + b); 
    } 
} 

MyClass myClass = new MyClass(); 

Ora, se voglio usare la mia interfaccia funzionale, posso:

Consumer2<String, String> printString = myClass::printStrings; 
printString.curry("hello").accept("world"); 

Ma non posso fare qualcosa di simile:

myClass::printStrings.curry("hello").accept("world"); 

che ha senso, perché Java non ha modo di sapere che myClass::printStrings può essere applicato all'interfaccia funzionale Consumer2. Per fare questo, ho creato una classe di utilità:

public class F { 
    public static <T1, T2> Consumer2<T1, T2> c2(Consumer2<T1, T2> fn) { 
     return fn; 
    } 
} 

Allora posso:

F.c2(myClass::printStrings).curry("hello").accept("world"); 

Anche, questo funzionerà:

((Consumer2<String, String>)myClass::printStrings).curry("hello").accept("world"); 

Finché c'è qualche modo per Java 8 per capire che tipo funzionale in questo caso. Quindi, la domanda è, qual è il modo migliore per farlo, evitando possibilmente il boilerplate?

+1

Credo classe 'Consumer2' si suppone essere' interfaccia Consumer2' ... – Holger

risposta

2

La soluzione che comprende il metodo F.c2 è interessante, ma il tuo esempio è troppo artificiale. Se chiedete, come scrivere meglio questo codice

F.c2(myClass::printStrings).curry("hello").accept("world"); 

Poi avrei sicuramente consigliare di scrivere in questo modo:

myClass.printStrings("hello", "world"); 

Se si vuole chiedere come associare i parametri predefiniti per il riferimento metodo , vi consiglio di utilizzare la funzione lambda invece:

Consumer1<String> fn = str -> myClass.printStrings("hello", str); 
fn.accept("world"); 

Probabilmente si vuole considerare il caso in cui la funzione non è noto al momento della compilazione. In questo caso viene restituito da un altro metodo o passato al metodo corrente come parametro del metodo o memorizzato nella variabile/campo. In tutti questi casi è già l'interfaccia funzionale ed è possibile utilizzare il curry direttamente:

Consumer2<String, String> getConsumer2() { return myClass::printStrings; } 

getConsumer2().curry("hello").accept("world"); 

Così, in generale, non vedo il problema qui. Se si pensa ancora che l'applicazione currying al riferimento di metodo è utile, vorrei creare un metodo statico curry (anche se credo, in realtà è un "vicolo cieco", non "curry") nell'interfaccia Consumer1:

static <T1, T2> Consumer1<T2> curry(Consumer2<T1, T2> c2, T1 t1) { 
    return c2.curry(t1); 
} 

E usalo in questo modo:

Consumer1.curry(myClass::printStrings, "hello").accept("world"); 
4

Non stai lavorando ma esegui un'applicazione di funzione parziale. Queste operazioni sono correlate, ma non identiche. Currying significa trasformare il tuo Consumer2<T1, T2> in un Function<T1,Consumer1<T2>>. Quando si applica la funzione al curry a un valore T1, si ottiene ciò che il metodo sta facendo in modo efficace.

È più facile utilizzare il nome stabilito bind poiché l'associazione di un valore al parametro di una funzione è qualcosa, ogni sviluppatore lo capisce senza dover immergersi profondamente nel mondo della programmazione funzionale.

Detto questo, è meglio ricordare che ora interface s può avere i metodi static, quindi non è necessario per tali classi di utilità. Inoltre, un metodo static che restituisce solo il suo argomento non è di sua utilità, quindi puoi fonderlo con il metodo di follow-up che dovrebbe servire. Quindi, è con lo stesso scopo come il metodo di istanza e può essere offerto come un semplice sovraccarico:

@FunctionalInterface 
public interface Consumer2<T1, T2> { 
    void accept(T1 t1, T2 t2); 

    default Consumer1<T2> bind(T1 t1) { 
     return bind(this, t1); 
    } 
    static <T,U> Consumer1<U> bind(Consumer2<? super T, ? super U> c, T t) { 
     return u -> c.accept(t, u); 
    } 
} 
public interface Consumer1<T1> extends Consumer<T1> {} 

public class MyClass { 
    public static void printStrings(String a, String b) { 
     System.out.println(a + ": " + b); 
    } 

    public static void main(String[] args) { 
     Consumer2.bind(MyClass::printStrings, "hello").accept("world"); 
    } 
} 

D'altra parte, quando si utilizza l'attuale standard di interface s Consumer e BiConsumer non hai scelta, ma di offrire un metodo di utilità in una classe diversa da questi interface s. Ma la buona notizia è che è facile da rendere la soluzione coerente, allora, come non si può fornire altro che un metodo static:

class FunctionUtil { 
    static <T,U> Consumer<U> bind(BiConsumer<? super T, ? super U> c, T t) { 
     return u -> c.accept(t, u); 
    } 
} 
public class MyClass { 
    public static void printStrings(String a, String b) { 
     System.out.println(a + ": " + b); 
    } 

    public static void main(String[] args) { 
     FunctionUtil.bind(MyClass::printStrings, "hello").accept("world"); 
    } 
}