Qual è la differenza tra il fornitoreT di CallableT e Java 8?

12

Ho passato a Java da C # dopo aver ricevuto alcuni suggerimenti da CodeReview. Quindi, quando stavo esaminando LWJGL, una cosa che ho ricordato è che ogni chiamata a Display deve essere eseguita sullo stesso thread su cui è stato richiamato il metodo Display.create() . Ricordando questo, ho montato una classe che assomiglia un po 'a questo.

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());
    }
}

Durante la scrittura di questo corso noterai che ho creato un metodo chiamato isClosed() che restituisce un Future<Boolean> . Questo invia una funzione alla mia interfaccia Scheduler (che non è altro che un wrapper attorno a ScheduledExecutorService . Mentre scrivevo il metodo schedule su Scheduler ho notato che potevo usare un argomento Supplier<T> o un argomento Callable<T> per rappresentare la funzione che è stata passata. ScheduledExecutorService non conteneva un override per Supplier<T> ma ho notato che l'espressione lambda () -> Display.isCloseRequested() è in realtà di tipo compatibile con entrambi Callable<bool> e Supplier<bool> .

La mia domanda è, c'è una differenza tra questi due, semanticamente o in altro modo - e se sì, che cos'è, quindi posso aderirvi?

    
posta Dan Pantry 24.08.2014 - 18:40
fonte

4 risposte

6

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

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

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

Conceptually, a functional interface has exactly one abstract method.

Pertanto, nella risposta di @Maciej Chalapuk , è anche possibile rilasciare l'annotazione e specificare il lambda desiderato :

// 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 sia Callable che Supplier interfacce funzionali è perché contengono esattamente un unico metodo astratto:

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

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

I noticed that the lambda expression () -> Display.isCloseRequested() is actually type compatible with both Callable<Boolean> and Supplier<Boolean>.

Come dovresti essere in grado di dedurre ormai, è solo perché entrambi i metodi astratti restituiranno il tipo di espressione che usi. Dovresti sicuramente utilizzare un Callable dato il tuo utilizzo di ScheduledExecutorService .

Ulteriore esplorazione (oltre lo scopo della domanda)

Entrambe le interfacce provengono interamente da diverse < a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html"> pacchetti , quindi sono usati anche in modo diverso. Nel tuo caso, non vedo come verrà utilizzata un'implementazione di Supplier<T> , a meno che non fornisca un Callable :

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

Il primo () -> può essere interpretato liberamente come "a Supplier dà ..." e il secondo come "a Callable dà ...". return value; è il corpo del lambda Callable , che a sua volta è il corpo del Supplier lambda.

Tuttavia, l'utilizzo in questo esempio forzato diventa un po 'complicato, poiché ora devi get() da Supplier prima di get() -tando il tuo risultato da Future , che a sua volta call() tuo Callable in modo asincrono.

public static <T> T doWork(Supplier<Callable<T>> callableSupplier) {
    // service being an instance of ExecutorService
    return service.submit(callableSupplier.get()).get();
}
    
risposta data 10.05.2015 - 13:52
fonte
17

Una differenza fondamentale tra le 2 interfacce è che Callable consente di generare eccezioni verificate dall'implementazione di esso, mentre il fornitore no.

Ecco i frammenti di codice dal 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();
}
    
risposta data 17.06.2016 - 05:42
fonte
8

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

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

È possibile che Callable possa fare pochissimo lavoro e restituire semplicemente un valore. È anche possibile che Supplier possa fare un bel po 'di lavoro (ad esempio, costruire una grande struttura dati). Ma in generale ciò che ti interessa con entrambi è il loro scopo principale. Ad esempio un ExecutorService funziona con Callable s, perché lo scopo principale è quello di eseguire unità di lavoro. Un data store pigro-caricato userebbe un Supplier , perché si preoccupa di essere fornito un valore, senza molta preoccupazione su quanto lavoro potrebbe richiedere.

Un altro modo di formulare la distinzione è che un Callable può avere effetti collaterali (ad esempio scrivere su un file), mentre un Supplier dovrebbe generalmente essere privo di effetti collaterali. La documentazione non menziona esplicitamente questo (poiché non è un requisito ), ma suggerirei di pensare in questi termini. Se il lavoro è idempotente utilizza Supplier , se non usi Callable .

    
risposta data 25.01.2016 - 21:57
fonte
2

Entrambe sono normali interfacce Java prive di semantica speciale. Callable è una parte dell'API simultanea. Fornitore fa parte della nuova API di programmazione funzionale. Possono essere creati da espressioni lambda grazie ai cambiamenti in Java8. @FunctionalInterface è un'annotazione di sola documentazione (in realtà non fa nulla) utilizzato per indicare che l'interfaccia è destinata a essere utilizzata in questo modo.

Puoi definire le tue interfacce compatibili con lambdas e documentarle con @FunctionalInterface annotation. La documentazione è facoltativa però.

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

...

MyInterface var = (int a) -> a != 0;
    
risposta data 24.08.2014 - 21:26
fonte

Leggi altre domande sui tag