web-dev-qa-db-fra.com

Quand faut-il utiliser Supplier en Java 8?

Quelle différence entre ce code?

Supplier<LocalDate> s1 = LocalDate::now;
LocalDate s2 = LocalDate.now();

System.out.println(s1.get()); //2016-10-25
System.out.println(s2); //2016-10-25

Je commence à apprendre les interfaces fonctionnelles en Java 8 et je ne comprends pas les avantages du fournisseur. Quand et comment, exactement, devrait les utiliser. Le fournisseur améliore-t-il les performances ou peut-être les avantages au niveau de l'abstraction ?

Merci pour vos réponses! Et ce n'est pas une question en double car j'ai utilisé la recherche et je n'ai pas trouvé ce dont j'avais besoin.

MISE À JOUR 1: Vous voulez dire cela?

    Supplier<Long> s1 = System::currentTimeMillis;
    Long s2 = System.currentTimeMillis();

    System.out.println(s1.get()); //1477411877817
    System.out.println(s2); //1477411877817
    try {
        Thread.sleep(3000l);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(s1.get()); //1477411880817 - different
    System.out.println(s2); //1477411877817
23
badCoder

Cela n'améliore certainement pas les performances. Votre question est similaire à celle-ci: pourquoi utilisons-nous des variables? Nous pourrions simplement recalculer tout à chaque fois que nous en avons besoin. Droite?

Si vous devez utiliser une méthode plusieurs fois, mais elle a une syntaxe verbeuse.

Supposons que vous ayez une classe nommée MyAmazingClass, et que vous ayez une méthode avec le nom MyEvenBetterMethod (qui est statique), et vous devez l'appeler 15 fois à 15 positions différentes dans votre code. Bien sûr, vous pouvez faire quelque chose comme ...

int myVar = MyAmazingClass.MyEvenBetterMethod();
// ...
int myOtherVar = MyAmazingClass.MyEvenBetterMethod();
// And so on...

... mais vous pouvez aussi faire

Supplier<MyAmazingClass> shorter = MyAmazingClass::MyEvenBetterMethod;

int myVar = shorter.get();
// ...
int myOtherVar = shorter.get();
// And so on...
17
Bálint

Je vais passer par un scénario où nous devrions utiliser Supplier<LocalDate> Au lieu de LocalDate.

Le code qui appelle directement des méthodes statiques comme LocalDate.now() est très difficile à tester unitaire. Considérons un scénario dans lequel nous voulons tester de manière unitaire une méthode getAge() qui calcule l'âge d'une personne:

class Person {
    final String name;
    private final LocalDate dateOfBirth;

    Person(String name, LocalDate dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }

    long getAge() {
        return ChronoUnit.YEARS.between(dateOfBirth, LocalDate.now());
    }
}

Cela fonctionne bien en production. Mais un test unitaire devrait soit définir la date du système sur une valeur connue, soit être mis à jour chaque année pour s'attendre à ce que l'âge retourné soit incrémenté d'une unité, deux solutions assez horribles.

Une meilleure solution serait d'injecter le test unitaire à une date connue tout en permettant au code de production d'utiliser LocalDate.now(). Peut-être quelque chose comme ça:

class Person {
    final String name;
    private final LocalDate dateOfBirth;
    private final LocalDate currentDate;

    // Used by regular production code
    Person(String name, LocalDate dateOfBirth) {
        this(name, dateOfBirth, LocalDate.now());
    }

    // Visible for test
    Person(String name, LocalDate dateOfBirth, LocalDate currentDate) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
        this.currentDate = currentDate;
    }

    long getAge() {
        return ChronoUnit.YEARS.between(dateOfBirth, currentDate);
    }

}

Prenons un scénario où l'anniversaire de la personne s'est écoulé depuis la création de l'objet. Avec cette implémentation, getAge() sera basé sur le moment où l'objet Person a été créé plutôt que sur la date actuelle. Nous pouvons résoudre ce problème en utilisant Supplier<LocalDate>:

class Person {
    final String name;
    private final LocalDate dateOfBirth;
    private final Supplier<LocalDate> currentDate;

    // Used by regular production code
    Person(String name, LocalDate dateOfBirth) {
        this(name, dateOfBirth, ()-> LocalDate.now());
    }

    // Visible for test
    Person(String name, LocalDate dateOfBirth, Supplier<LocalDate> currentDate) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
        this.currentDate = currentDate;
    }

    long getAge() {
        return ChronoUnit.YEARS.between(dateOfBirth, currentDate.get());
    }

    public static void main(String... args) throws InterruptedException {
        // current date 2016-02-11
        Person person = new Person("John Doe", LocalDate.parse("2010-02-12"));
        printAge(person);
        TimeUnit.DAYS.sleep(1);
        printAge(person);
    }

    private static void printAge(Person person) {
        System.out.println(person.name + " is " + person.getAge());
    }
}

La sortie serait correctement:

John Doe is 5
John Doe is 6

Notre test unitaire peut injecter la date "maintenant" comme ceci:

@Test
void testGetAge() {
    Supplier<LocalDate> injectedNow = ()-> LocalDate.parse("2016-12-01");
    Person person = new Person("John Doe", LocalDate.parse("2004-12-01"), injectedNow);
    assertEquals(12, person.getAge());
}
14
joshden

Vous confondez les interfaces fonctionnelles et les références de méthode. Supplier n'est qu'une interface, similaire à Callable, que vous devriez connaître depuis Java 5, la seule différence étant que Callable.call est autorisé à lancer des Exceptions vérifiés, contrairement à Supplier.get. Ces interfaces auront donc des cas d'utilisation similaires.

Maintenant, ces interfaces se trouvent également être interfaces fonctionnelles, ce qui implique qu'elles peuvent être implémentées comme référence de méthode, pointant vers une méthode existante qui sera invoquée lorsque la méthode d'interface sera invoquée.

Donc avant Java 8, il fallait écrire

Future<Double> f=executorService.submit(new Callable<Double>() {
    public Double call() throws Exception {
        return calculatePI();
    }
});
/* do some other work */
Double result=f.get();

et maintenant, vous pouvez écrire

Future<Double> f=executorService.submit(() -> calculatePI());
/* do some other work */
Double result=f.get();

ou

Future<Double> f=executorService.submit(MyClass::calculatePI);
/* do some other work */
Double result=f.get();

La question quand utiliserCallable n'a pas changé du tout.

De même, la question de savoir quand utiliser Supplier ne dépend pas de la façon dont vous l'implémentez, mais de l'API que vous utilisez, c'est-à-dire.

CompletableFuture<Double> f=CompletableFuture.supplyAsync(MyClass::calculatePI);
/* do some other work */
Double result=f.join();// unlike Future.get, no checked exception to handle...
9
Holger

Je suis sûr que votre réponse a déjà été répondue par les réponses fournies. C'est ma compréhension du fournisseur.

Il me donne la valeur par défaut Java interfaces définies qui agissent comme un wrapper pour tout temps de retour cible de la méthode lambda. Autrement dit, partout où vous avez écrit une méthode qui n'a pas d'arguments et renvoie un objet, se qualifie à être enveloppé par l'interface du fournisseur. Sans cela, dans les pré-génériques, vous captureriez le retour en tant que classe d'objet, puis vous convertiriez et en génériques, vous définiriez votre propre T(ype) pour contenir le Il fournit simplement un support générique de type de retour uniforme sur lequel vous pouvez appeler get () pour extraire l'objet renvoyé de la méthode no arg mentionnée ci-dessus.

1
Ed Bighands

J'ajouterai mon point de vue car je ne suis pas satisfait des réponses: le fournisseur est utile lorsque vous souhaitez retarder l'exécution.

Sans fournisseur

config.getLocalValue(getFromInternet() /*value if absent*/);

Avant que getLocalValue ne soit appelée, getFromInternet sera appelé, mais la valeur de getFromInternet () sera utilisée uniquement si la valeur locale est absente.

Maintenant si config.getLocalValue peut accepter le fournisseur, nous pouvons retarder cette exécution, plus nous n'exécuterons pas si la valeur locale est présente.

config.getLocalValue(() -> getFromInternet())

Différence Le fournisseur permet: execute only when and if needed

0
Vivek MVK

L'explication de l'article 5 de Effectif Java par Joshua Bloch m'a éclairci la question:

L'interface fournisseur, introduite dans Java 8, est parfaite pour représenter les usines. Les méthodes qui prennent un fournisseur en entrée doivent généralement contraindre le paramètre de type de l'usine en utilisant un type générique délimité (article 31) pour permettre la client à passer dans une fabrique qui crée tout sous-type d'un type spécifié. Par exemple, voici une méthode qui crée une mosaïque à l'aide d'une fabrique fournie par le client pour produire chaque mosaïque:

Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

Vous voudrez peut-être envoyer une fabrique de ressources à un objet qui créera des objets à l'aide de la fabrique de ressources. Supposons maintenant que vous souhaitiez envoyer différentes fabriques de ressources avec chaque fabrique de ressources générant des objets à différentes positions de l'arborescence d'héritage et que l'objet qui reçoit la fabrique de ressources doit pouvoir les recevoir dans tous les cas.

Ensuite, vous pouvez implémenter un fournisseur qui peut accepter des méthodes qui retournent des objets étendant/implémentant une certaine classe/interface en utilisant le caractère générique borné et utiliser le fournisseur comme fabrique de ressources.

La lecture du point 5 du livre pourrait aider à comprendre complètement l'utilisation.

0
Htnamus