Qual è la differenza tra Callable <T> e Java 8's Supplier <T>?


13

Sto passando a Java da C # dopo alcuni consigli da alcuni su CodeReview. Quindi, quando stavo esaminando LWJGL, una cosa che ricordavo era che ogni chiamata a Displaydoveva essere eseguita sullo stesso thread su cui Display.create()era stato invocato il metodo. Ricordando questo, ho preparato una classe che assomiglia un po 'a questa.

public class LwjglDisplayWindow implements DisplayWindow {
    private final static int TargetFramesPerSecond = 60;
    private final Scheduler _scheduler;

    public LwjglDisplayWindow(Scheduler displayScheduler, DisplayMode displayMode) throws LWJGLException {
        _scheduler = displayScheduler;
        Display.setDisplayMode(displayMode);
        Display.create();
    }

    public void dispose() {
        Display.destroy();
    }

    @Override
    public int getTargetFramesPerSecond() { return TargetFramesPerSecond; }

    @Override
    public Future<Boolean> isClosed() {
        return _scheduler.schedule(() -> Display.isCloseRequested());
    }
}

Mentre scrivi questa classe noterai che ho creato un metodo chiamato isClosed()che restituisce a Future<Boolean>. Questo invia una funzione alla mia Schedulerinterfaccia (che non è altro che un wrapper attorno a un ScheduledExecutorService. Mentre scrivevo il schedulemetodo su Schedulerho notato che avrei potuto usare un Supplier<T>argomento o un Callable<T>argomento per rappresentare la funzione che è passata. ScheduledExecutorServiceNon conteneva un override for, Supplier<T>ma ho notato che l'espressione lambda () -> Display.isCloseRequested()è in realtà compatibile con entrambi i tipi Callable<bool> e Supplier<bool> .

La mia domanda è: c'è una differenza tra quei due, semanticamente o in altro modo - e in tal caso, che cos'è, quindi posso aderire ad esso?


Ero sotto il codice delle impressioni che non funziona = SO, codice che funziona ma ha bisogno di review = CodeReview, domande generali che potrebbero o non potrebbero aver bisogno di code = programmatori. Il mio codice funziona davvero ed è presente solo come esempio. Non sto nemmeno chiedendo una recensione, sto solo chiedendo della semantica.
Dan Pantry,

..pagare sulla semantica di qualcosa non è una domanda concettuale?
Dan Pantry,

Penso che questa sia una domanda concettuale, non concettuale come altre buone domande su questo sito ma non riguarda l'implementazione. Il codice funziona, la domanda non riguarda il codice. La domanda è "qual è la differenza tra queste due interfacce?"

Perché vorresti passare da C # a Java!
Didier A.

2
C'è una differenza, vale a dire che Callable.call () genera eccezioni e Supplier.get () no. Ciò rende quest'ultimo molto più attraente nelle espressioni lambda.
Thorbjørn Ravn Andersen,

Risposte:


6

La risposta breve è che entrambi utilizzano interfacce funzionali, ma è anche degno di nota che non tutte le interfacce funzionali devono avere l' @FunctionalInterfaceannotazione. La parte critica di JavaDoc recita:

Tuttavia, il compilatore tratterà qualsiasi interfaccia che soddisfa la definizione di un'interfaccia funzionale come un'interfaccia funzionale indipendentemente dal fatto che sia presente o meno un'annotazione FunctionalInterface nella dichiarazione dell'interfaccia.

E la definizione più semplice di un'interfaccia funzionale è (semplicemente, senza altre esclusioni) solo:

Concettualmente, un'interfaccia funzionale ha esattamente un metodo astratto.

Pertanto, nella risposta di @Maciej Chalapuk , è anche possibile eliminare l'annotazione e specificare la lambda desiderata:

// interface
public interface MyInterface {
    boolean myCall(int arg);
}

// method call
public boolean invokeMyCall(MyInterface arg) {
    return arg.myCall(0);
}

// usage
instance.invokeMyCall(a -> a != 0); // returns true if the argument supplied is not 0

Ora, ciò che rende entrambi Callablee Supplierfunzionali interfacce è perché non contengono esattamente un metodo astratto:

  • Callable.call()
  • Supplier.get()

Poiché entrambi i metodi non accettano un argomento (al contrario MyInterface.myCall(int)dell'esempio), i parametri formali sono vuoti ( ()).

Ho notato che l'espressione lambda () -> Display.isCloseRequested()è in realtà il tipo compatibile con entrambi Callable<Boolean> e Supplier<Boolean> .

Come dovresti essere in grado di dedurre ormai, questo è solo perché entrambi i metodi astratti restituiranno il tipo di espressione che usi. Dovresti sicuramente usare un Callabledato tuo uso di a ScheduledExecutorService.

Ulteriore esplorazione (oltre l'ambito della domanda)

Entrambe le interfacce provengono da pacchetti completamente diversi , quindi vengono utilizzate anche in modo diverso. Nel tuo caso, non vedo come verrà utilizzata un'implementazione di , a meno che non stia fornendo un :Supplier<T>Callable

public static <T> Supplier<Callable<T>> getCallable(T value) {
    return () -> () -> {
        return value;
    };
}

Il primo () ->può essere liberamente interpretato come "a Supplierdà ..." e il secondo come "a Callabledà ...". return value;è il corpo del Callablelambda, che è esso stesso il corpo del Supplierlambda.

Tuttavia, l'utilizzo in questo esempio inventato ottiene un po 'complicata, come è ora necessario get()dalla Supplierprima get()-ting il risultato dalla Future, che a sua volta call()il vostro Callablein modo asincrono.

public static <T> T doWork(Supplier<Callable<T>> callableSupplier) {
    // service being an instance of ExecutorService
    return service.submit(callableSupplier.get()).get();
}

1
Sto cambiando risposta accettata a questa risposta perché questa è semplicemente molto più completa
Dan Pantry,

Più a lungo non equivale a più utile, vedi la risposta di @ srrm_lwn.
SensorSmith

La risposta srrms di @SensorSmith era quella originale che ho contrassegnato come risposta accettata. Penso ancora che questo sia più utile.
Dan Pantry,

21

Una differenza fondamentale tra le 2 interfacce è che Callable consente di generare eccezioni verificate all'interno dell'implementazione, mentre il Fornitore non lo fa.

Ecco i frammenti di codice del JDK che evidenziano questo -

@FunctionalInterface
public interface Callable<V> {
/**
 * Computes a result, or throws an exception if unable to do so.
 *
 * @return computed result
 * @throws Exception if unable to compute a result
 */
V call() throws Exception;
}

@FunctionalInterface
public interface Supplier<T> {

/**
 * Gets a result.
 *
 * @return a result
 */
T get();
}

Ciò rende inutilizzabile Callable come argomento nelle interfacce funzionali.
Basilevs,

3
@Basilevs no, non lo è - non è utilizzabile solo in luoghi che prevedono un Supplier, come l'API dello stream. Puoi assolutamente passare lambda e riferimenti a metodi che accettano a Callable.
dimo414

12

Come notate, in pratica fanno la stessa cosa (forniscono una sorta di valore), tuttavia in linea di principio sono destinati a fare cose diverse:

A Callableè " Un'attività che restituisce un risultato , mentre a Supplierè" un fornitore di risultati ". In altre parole, a Callableè un modo per fare riferimento a un'unità di lavoro non ancora eseguita, mentre a Supplierè un modo per fare riferimento a un valore ancora sconosciuto.

È possibile che a Callablepossa fare pochissimo lavoro e semplicemente restituire un valore. È anche possibile che si Supplierpossa fare parecchio lavoro (ad esempio costruire una grande struttura di dati). Ma in generale ciò di cui ti preoccupi è il loro scopo principale. Ad esempio, ExecutorServicefunziona con Callables, poiché il suo scopo principale è quello di eseguire unità di lavoro. Un archivio dati pigro-caricato userebbe un Supplier, perché si preoccupa di essere fornito un valore, senza molta preoccupazione per la quantità di lavoro che potrebbe richiedere.

Un altro modo di esprimere la distinzione è che a Callablepuò avere effetti collaterali (ad es. Scrivere su un file), mentre a Supplierdovrebbe essere generalmente privo di effetti collaterali. La documentazione non menziona esplicitamente questo (dal momento che non è un requisito ), ma suggerirei di pensare in questi termini. Se il lavoro è idempotente usa a Supplier, se non usa a Callable.


2

Entrambe sono normali interfacce Java senza semantica speciale. Callable è una parte dell'API simultanea. Il fornitore fa parte della nuova API di programmazione funzionale. Possono essere creati dalle espressioni lambda grazie alle modifiche in Java8. @FunctionalInterface fa verificare al compilatore che l'interfaccia sia funzionante e genera un errore se non lo è, ma un'interfaccia non ha bisogno di quell'annotazione per essere un'interfaccia funzionale ed essere implementata da lambdas. Questo è come un metodo può essere un override senza essere contrassegnato con @Override ma non viceversa.

È possibile definire le proprie interfacce compatibili con lambdas e documentarle con @FunctionalInterfaceannotazioni. Documentare è facoltativo però.

@FunctionalInterface
public interface MyInterface {
    boolean myCall(int arg);
}

...

MyInterface var = (int a) -> a != 0;

Anche se vale la pena notare che questa particolare interfaccia è chiamata IntPredicatein Java.
Konrad Borowski,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.