2016-03-28 6 views
14

Ho appena iniziato ad esplorare alcune caratteristiche di concorrenza di Java 8. Una cosa mi ha confuso un po 'è che questi due metodi statici:CompletableFuture supplyAsync

CompletableFuture<Void> runAsync(Runnable runnable) 
CompletableFuture<U> supplyAsync(Supplier<U> supplier) 

Do qualcuno sa il motivo per cui si sceglie di utilizzare l'interfaccia del fornitore? Non è più naturale usare Callable, che è l'analogia di Runnable che restituisce un valore? È perché il Fornitore non genera un'eccezione che non può essere gestita?

+0

perché avevano appena di essere diverso da C#, che è genericamente chiamato 'Azione <>' e ' Interfacce <> che fanno lo stesso lavoro ma senza confondere il programmatore. E così hanno proliferato interfacce funzionali per metodi con le stesse firme ma nomi diversi. Probabilmente pensavano che avrebbe aiutato il programmatore dandogli un modello mentale con cui lavorare, ma non lo è: è solo un PITA minore. – davidbak

risposta

7

Risposta breve

No, non è più naturale da usare al posto di CallableSupplier in CompletableFuture.supplyAsync. L'argomento riguarda quasi esclusivamente la semantica, quindi va bene se in seguito ti senti ancora poco convinto.

Long risposta

I/tipi SAM Callable e Supplier interfacce funzionali sono praticamente equivalenti in funzione (noir), ma la loro origine e destinazione d'uso differiscono.

Callable è stato creato come parte del pacchetto java.util.concurrent. Questo pacchetto è venuto prima dei vasti cambiamenti relativi alle espressioni lambda in Java 8 e si è inizialmente concentrato su una serie di strumenti che hanno aiutato a scrivere codice concorrente, senza deviare molto dal classico modello di multithreading pratico.

Lo scopo principale di Callable era di astrarre un'azione che può essere eseguita in un thread diverso e che restituisce un risultato. Da Javadoc Callable s':

L'interfaccia Callable è simile a Runnable, dal fatto che entrambi sono progettati per classi le cui istanze sono potenzialmente eseguita da altro thread.

Supplier è stato creato come parte del pacchetto java.util.function. Quel pacchetto è venuto come parte integrante delle suddette modifiche in Java 8. Fornisce tipi funzionali comuni che possono essere presi di mira da espressioni lambda e riferimenti al metodo.

Uno di questi tipi è una funzione senza parametri che restituisce un risultato (ad esempio una funzione che fornisce un tipo o una funzione Supplier).

Quindi perché Supplier e non Callable?

CompletableFuture fa parte di aggiunte al pacchetto java.util.concurrent ispirate delle citate modifiche in Java 8 e che consentono allo sviluppatore di costruire il suo codice in maniera funzionale, implicitamente parallelizzabile, invece di gestire esplicitamente concorrenza all'interno di esso.

Il suo metodo supplyAsync ha bisogno di un modo per fornire un risultato di un tipo specifico e più interessato a questo risultato, e non all'azione intrapresa per raggiungere questo risultato. Inoltre, non si preoccupa necessariamente del completamento eccezionale (vedi anche il paragrafo e il ... seguente).

Tuttavia, se viene utilizzato per Runnable senza parametri, senza risultato interfaccia funzionale, non deve essere usato per Callable senza parametri, interfaccia funzionale unico risultato?

Non necessariamente.

Un'astrazione per una funzione che non ha un parametro e non restituisce un risultato (e quindi opera interamente tramite effetti collaterali sul contesto esterno) non è stata inclusa in java.util.function. Ciò significa che (un po 'fastidiosamente) Runnable viene utilizzato ovunque sia necessaria un'interfaccia funzionale di questo tipo.

E il Exception controllato che può essere lanciato da Callable.call()?

È un piccolo segno della differenza semantica prevista tra Callable e Supplier.

A Callable è un'azione che può essere eseguita in un altro thread e che consente di controllare i suoi effetti collaterali come risultato della sua esecuzione. Se tutto va bene, si ottiene un risultato di un tipo specifico, ma poiché possono sorgere situazioni eccezionali durante l'esecuzione di alcune azioni (specialmente nel contesto multithread), è possibile anche definire e gestire tali situazioni eccezionali.

A Supplier d'altra parte è una funzione su cui ci si basa per fornire oggetti di qualche tipo. Le situazioni eccezionali non devono essere necessariamente considerate responsabilità dell'utente diretto di Supplier. Questo perché:

  1. ... interfacce funzionali sono spesso utilizzati per la definizione di una fase specifica in un processo a più fasi per la creazione o la mutazione dei dati e la manipolazione Exception s può essere una fase separata, nel caso in cui si cura
  2. ... in modo esplicito la gestione Exception s riduce in modo significativo i poteri espressivi di interfacce funzionali, le espressioni lambda e il metodo fa riferimento
+0

Penso che i tuoi argomenti che la differenza principale si trova sul lato semantico, il lato non pratico è corretto. Usare semanticamente un metodo chiamato _CallAsync_ non implica nulla che restituisca un risultato. –

+0

IMO l'idea di avere chiamato separatamente interfacce funzionali con metodi _different per la stessa funzionalità_ (ad esempio, 'call()' vs 'get()') era mal concepita e porta a molti fastidi. Due in particolare sono: cercando di ricordare quale sia il metodo darn che è necessario chiamare e quindi non essere facilmente in grado di inviare un fornitore a un metodo che ha bisogno di Callable, ecc. Ecc. Le "semantiche" che intendi descrivere sono tutte in parole e non ha nulla a che fare con nulla che il compilatore avrebbe imposto e sono mal descritti o non descritti affatto nei documenti Java. C# è stato meglio. – davidbak

Problemi correlati