Quelle est la différence entre Callable <T> et Java 8's Supplier <T>?

13

Je suis passé à Java à partir de C # après quelques recommandations de la part de CodeReview. Donc, quand je regardais LWJGL, une chose dont je me souvenais était que chaque appel à Displaydevait être exécuté sur le même thread que la Display.create()méthode était invoquée. En me souvenant de cela, j'ai concocté un cours qui ressemble un peu à ça.

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

En écrivant cette classe, vous remarquerez que j'ai créé une méthode appelée isClosed()qui renvoie a Future<Boolean>. Cela envoie une fonction à mon Schedulerinterface (qui n'est rien de plus qu'un wrapper autour d'un ScheduledExecutorService. Lors de l'écriture de la scheduleméthode sur le Schedulerj'ai remarqué que je pouvais soit utiliser un Supplier<T>argument ou un Callable<T>argument pour représenter la fonction transmise. ScheduledExecutorServiceNe contenait pas de remplacer pour Supplier<T>mais j'ai remarqué que l'expression lambda () -> Display.isCloseRequested()est en fait compatible avec les deux types Callable<bool> et Supplier<bool> .

Ma question est, y a-t-il une différence entre ces deux, sémantiquement ou autrement - et si oui, qu'est-ce que c'est, pour que je puisse y adhérer?

Dan Pantry
la source
J'étais sous le code d'impression qui ne fonctionne pas = SO, le code qui fonctionne mais doit être révisé = CodeReview, des questions générales qui peuvent ou non nécessiter du code = programmeurs. Mon code fonctionne réellement et n'est là qu'à titre d'exemple. Je ne demande pas non plus de révision, je me contente de poser des questions sur la sémantique.
Dan Pantry
..la question de la sémantique de quelque chose n'est pas une question conceptuelle?
Dan Pantry
Je pense que c'est une question conceptuelle, pas aussi conceptuelle que d'autres bonnes questions sur ce site, mais ce n'est pas une question de mise en œuvre. Le code fonctionne, la question ne concerne pas le code. La question est de se demander "quelle est la différence entre ces deux interfaces?"
Pourquoi voudriez-vous passer de C # à Java!
Didier A.
2
Il y a une différence, à savoir que Callable.call () lève des exceptions et Supplier.get () non. Cela rend ce dernier beaucoup plus attrayant dans les expressions lambda.
Thorbjørn Ravn Andersen

Réponses:

6

La réponse courte est que les deux utilisent des interfaces fonctionnelles, mais il convient également de noter que toutes les interfaces fonctionnelles ne doivent pas avoir l' @FunctionalInterfaceannotation. La partie critique de JavaDoc se lit comme suit:

Cependant, le compilateur traitera toute interface répondant à la définition d'une interface fonctionnelle comme une interface fonctionnelle, qu'une annotation FunctionalInterface soit présente ou non dans la déclaration d'interface.

Et la définition la plus simple d'une interface fonctionnelle est (simplement, sans autres exclusions) juste:

Conceptuellement, une interface fonctionnelle a exactement une méthode abstraite.

Par conséquent, dans la réponse de @Maciej Chalapuk , il est également possible de supprimer l'annotation et de spécifier le lambda souhaité:

// 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

Maintenant, ce qui fait les deux Callableet Supplierles interfaces fonctionnelles, c'est qu'elles contiennent exactement une méthode abstraite:

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

Puisque les deux méthodes n'acceptent pas d'argument (contrairement à l' MyInterface.myCall(int)exemple), les paramètres formels sont empty ( ()).

J'ai remarqué que l'expression lambda () -> Display.isCloseRequested()est en fait compatible avec les deux types Callable<Boolean> et Supplier<Boolean> .

Comme vous devriez pouvoir en déduire maintenant, c'est simplement parce que les deux méthodes abstraites renverront le type de l'expression que vous utilisez. Vous devriez certainement utiliser un Callabledonné votre utilisation d'un ScheduledExecutorService.

Poursuite de l'exploration (au-delà de la portée de la question)

Les deux interfaces proviennent de packages entièrement différents , ils sont donc également utilisés différemment. Dans votre cas, je ne vois pas comment une implémentation de sera utilisée, sauf si elle fournit :Supplier<T>Callable

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

Le premier () ->peut être interprété librement comme "un Supplierdonne ..." et le second comme "un Callabledonne ...". return value;est le corps de la Callablelambda, qui est lui-même le corps de la Supplierlambda.

Cependant, l' utilisation dans cet exemple artificiel devient légèrement compliqué, que vous avez besoin maintenant get()de la Supplierpremière avant get()Opération permettant le résultat de la Future, qui à son tour call()votre Callablemanière asynchrone.

public static <T> T doWork(Supplier<Callable<T>> callableSupplier) {
    // service being an instance of ExecutorService
    return service.submit(callableSupplier.get()).get();
}
hjk
la source
1
Je change de réponse acceptée à cette réponse car elle est tout simplement beaucoup plus complète
Dan Pantry
Plus long ne signifie pas plus utile, voir la réponse de @ srrm_lwn.
SensorSmith
La réponse srrms @SensorSmith était l'original que j'ai marqué comme réponse acceptée. Je pense toujours que celui-ci est plus utile.
Dan Pantry
21

Une différence fondamentale entre les 2 interfaces est que Callable permet de lever les exceptions vérifiées depuis son implémentation, contrairement au fournisseur.

Voici les extraits de code du JDK soulignant cela -

@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();
}
srrm_lwn
la source
Cela rend Callable inutilisable comme argument dans les interfaces fonctionnelles.
Basilevs
3
@Basilevs non, ce n'est pas le cas - il n'est tout simplement pas utilisable dans les endroits qui attendent un Supplier, comme l'API de flux. Vous pouvez absolument transmettre des lambdas et des références de méthode à des méthodes qui prennent un Callable.
dimo414
12

Comme vous le constatez, en pratique, ils font la même chose (fournissent une sorte de valeur), mais en principe, ils sont destinés à faire des choses différentes:

A Callableest " Une tâche qui renvoie un résultat , tandis que a Supplierest" un fournisseur de résultats ". En d'autres termes, a Callableest un moyen de référencer une unité de travail non encore exécutée, tandis que a Supplierest un moyen de référencer une valeur encore inconnue.

Il est possible que a Callablefasse très peu de travail et renvoie simplement une valeur. Il est également possible que l'on Supplierpuisse faire beaucoup de travail (par exemple, construire une grande structure de données). Mais de manière générale, ce qui vous intéresse, c'est leur objectif principal. Par exemple, un ExecutorServicefonctionne avec Callables, car son objectif principal est d' exécuter des unités de travail. Un magasin de données chargé paresseux utiliserait un Supplier, parce qu'il prend soin d'être fourni une valeur, sans trop se soucier de la façon dont beaucoup de travail qui pourrait prendre.

Une autre façon de formuler la distinction est que a Callablepeut avoir des effets secondaires (par exemple, écrire dans un fichier), tandis que a Supplierdevrait généralement être exempt d'effets secondaires. La documentation ne le mentionne pas explicitement (car ce n'est pas une exigence ), mais je suggère de penser en ces termes. Si l'œuvre est idempotente, utilisez a Supplier, sinon utilisez a Callable.

dimo414
la source
2

Les deux sont des interfaces Java normales sans sémantique particulière. Callable fait partie de l'API simultanée. Le fournisseur fait partie de la nouvelle API de programmation fonctionnelle. Ils peuvent être créés à partir d'expressions lambda grâce aux modifications de Java8. @FunctionalInterface fait vérifier au compilateur que l'interface est fonctionnelle et déclenche une erreur si ce n'est pas le cas, mais une interface n'a pas besoin de cette annotation pour être une interface fonctionnelle et être implémentée par lambdas. Cela ressemble à la façon dont une méthode peut être un remplacement sans être marquée @Override mais pas vice-versa.

Vous pouvez définir vos propres interfaces compatibles avec lambdas et les documenter avec des @FunctionalInterfaceannotations. La documentation est cependant facultative.

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

...

MyInterface var = (int a) -> a != 0;
Maciej Chałapuk
la source
Bien qu'il soit intéressant de noter cette interface particulière est appelée IntPredicateen Java.
Konrad Borowski