Quando dovrei creare il mio @FunctionalInterface anziché riutilizzare le interfacce definite in java.util.function?

4

Le interfacce funzionali in java.util.function coprire la maggior parte delle strategie comuni che si potrebbero applicare, ma è anche possibile definire il proprio @FunctionalInterface . Nei casi in cui l'interfaccia desiderata coincide con un'interfaccia esistente in java.util.function , si preferisce definire un'interfaccia personalizzata per il riutilizzo? Perché o perché no?

Come esempio concreto (anche se sto cercando risposte alla domanda generale), dovrebbe una funzione che si aspetta un'operazione di ordinamento prende un Consumer<List<T>> e stabilisce che ordina l'input o deve essere definita un'interfaccia separata che specifica un metodo sort(List<T>) ?

    
posta dimo414 23.04.2017 - 04:28
fonte

1 risposta

5

Prima di tutto questo è in gran parte una questione di stile e design dell'API, perché i chiamanti possono sempre convertire da un'interfaccia funzionale equivalente a un'altra tramite i riferimenti al metodo. Pertanto, se hai un'istanza Consumer<T> e devi passarla a un metodo che prevede un MyConsumer<T> , puoi farlo come:

myFunction(someConsumer::accept);

Poiché è una questione di stile, le persone ragionevoli non sono d'accordo, ma ecco cosa suggerirei di considerare:

  • Il tuo obiettivo come l'autore dell'API dovrebbe essere quello di ridurre al minimo le opportunità di abuso.
  • Le interfacce definite in java.util.function sono general-purpose, il che significa che i loro contratti API sono molto deboli. In sostanza non stipulano nient'altro che il significato delle loro firme metodologiche, il che può portare a errori se in realtà hai requisiti più severi.
  • Un'interfaccia che crei può definire un contratto più rigido, che può aiutare gli utenti quando tentano di utilizzare il tuo codice. Come sottolinea Jules, la semplice definizione di un'interfaccia più precisa può migliorare significativamente la leggibilità e l'usabilità della tua API.
  • Detto questo, gli utenti sono obbligati a leggere il contratto dell'interfaccia per assicurarsi che lo seguano correttamente, il che rappresenta un ulteriore onere e può anche essere fonte di errori.

Tenendo presenti queste considerazioni, suggerirei di definire la tua interfaccia ogni volta che il tuo codice richiede più dei suoi chiamanti rispetto a quanto implicano le interfacce generiche.

Ad esempio, un metodo che si aspetta che una strategia di ordinamento non si comporti correttamente se ha passato un consumatore che non ordina effettivamente gli input, il che significa che accettare implementazioni arbitrarie di Consumer<List<T>> non è auspicabile e specificare il proprio Sorter l'interfaccia rende la tua API più chiara e meno probabile che venga erroneamente utilizzata in modo improprio.

D'altra parte molte operazioni funzionali / strategiche sono pienamente compatibili con i contratti delle interfacce generiche, nel qual caso non dovresti esitare a riutilizzarle. Anche se alcune implementazioni non sono "corrette" di per sé, purché il codice si comporti ancora correttamente quando vengono passate tali implementazioni, non è necessario rendere il contratto più complesso definendo la propria interfaccia.

Come altro esempio, considera Stream.map() - l'intento di questa funzione è di applicare qualche operazione all'input e restituire il risultato trasformato, ma non c'è nulla che impedisca a un chiamante di passare un'operazione che in realtà non lo fa, come uno che restituisce solo un risultato arbitrario, ignorando l'input:

myIntStream.map(i -> random.nextInt())

Questo non è in linea con lo spirito di map() (ed è uno strano modo per generare n ints ...), ma non infrange nulla - .map() si comporta ancora correttamente e come previsto lo stream ora contiene un numero di numeri interi casuali. Pertanto è ragionevole che .map() abbia un Function arbitrario e non un'interfaccia più definita.

    
risposta data 23.04.2017 - 05:01
fonte

Leggi altre domande sui tag