Différence entre `Optional.orElse ()` et `Optional.orElseGet ()`


206

J'essaie de comprendre la différence entre les méthodes Optional<T>.orElse()et Optional<T>.orElseGet().

La description de la orElse()méthode est "Renvoyer la valeur si présente, sinon renvoyer autre".

Tandis que la description de la orElseGet()méthode est "Renvoyer la valeur si elle est présente, sinon invoquer autre et renvoyer le résultat de cette invocation".

La orElseGet()méthode prend une interface fonctionnelle fournisseur, qui ne prend essentiellement aucun paramètre et retourne T.

Dans quelle situation auriez-vous besoin d'utiliser orElseGet()? Si vous avez une méthode, T myDefault()pourquoi ne le feriez-vous pas optional.orElse(myDefault())plutôt que optional.orElseGet(() -> myDefault())?

Il ne semble pas que cela orElseGet()retarde l'exécution de l'expression lambda à un moment ultérieur ou quelque chose comme ça, alors à quoi ça sert? (J'aurais pensé qu'il serait plus utile s'il renvoyait un coffre-fort Optional<T>dont get()jamais ne lance un NoSuchElementExceptionet isPresent()renvoie toujours vrai ... mais évidemment ce n'est pas le cas, il revient juste Tcomme orElse()).

Y a-t-il une autre différence qui me manque?


7
La raison en est que lorsque vous l'utilisez, orElseGetil n'appelle le fournisseur que si la valeur est absente.
Alex Salauyou

9
Ah ok, j'ai compris. Donc, dans le cas de orElse()la myDefault()méthode est toujours appelée, mais sa valeur de retour n'est tout simplement pas utilisée.
jbx

3
Question votée parce que d'après ce que j'ai vu un malentendu ou simplement oublier d'utiliser orElseGet()peut entraîner de graves bugs: medium.com/alphadev-oughtts/…
softarn

Réponses:


172

Prenez ces deux scénarios:

Optional<Foo> opt = ...
Foo x = opt.orElse( new Foo() );
Foo y = opt.orElseGet( Foo::new );

Si optne contient pas de valeur, les deux sont en effet équivalents. Mais si opt ne contient une valeur, combien Fooseront créés des objets?

Ps: bien sûr, dans cet exemple, la différence ne serait probablement pas mesurable, mais si vous devez obtenir votre valeur par défaut à partir d'un service Web distant par exemple, ou d'une base de données, cela devient soudain très important.


22
Merci pour les éclaircissements. La différence est donc subtile mais significative. Dans le second cas, il ne créera pas de nouvel Fooobjet, tandis que dans le premier cas il le créera, mais ne l'utilisera pas s'il y a une valeur à l'intérieur de Optional.
jbx

5
@jbx Oui, et dans mon exemple noddy, cela ne fait sans doute aucune différence réelle, mais si vous devez obtenir votre valeur par défaut à partir d'un service Web distant par exemple, ou d'une base de données, la différence devient soudainement très importante.
biziclop

2
@jbx: vous mélangez deux choses. Il y a déjà des questions sur le SO concernant les résultats de référence étranges qui ont simplement été causés en n'utilisant pas le résultat d'un calcul. La JVM peut le faire. D'un autre côté, ce System.out.println()n'est pas un calcul mais une déclaration produisant un effet secondaire observable. Et j'ai déjà dit que les effets secondaires observables entraveraient les optimisations (le flux de sortie de la console est une ressource externe).
Holger

7
C'est la première fois que je vois une question au lieu d'une réponse acceptée.
Kirill G.

4
" si vous devez obtenir votre valeur par défaut à partir d'un service Web distant par exemple ", c'était exactement mon scénario. Dans mon cas, l'option était une requête, et la valeur par défaut en l'absence de requête était de récupérer toutes les valeurs ... ouais, orElseGet a réduit le temps d'exécution de cette opération de 1000 fois.
scottysseus

109

Réponse courte:

  • orElse () appellera toujours la fonction donnée, que vous le vouliez ou non, quelle que soit la Optional.isPresent()valeur
  • orElseGet () n'appellera la fonction donnée que lorsque leOptional.isPresent() == false

Dans le code réel, vous voudrez peut-être envisager la deuxième approche lorsque la ressource requise est coûteuse à obtenir .

// Always get heavy resource
getResource(resourceId).orElse(getHeavyResource()); 

// Get heavy resource when required.
getResource(resourceId).orElseGet(() -> getHeavyResource()) 

Pour plus de détails, considérez l'exemple suivant avec cette fonction:

public Optional<String> findMyPhone(int phoneId)

La différence est comme ci-dessous:

                           X : buyNewExpensivePhone() called

+——————————————————————————————————————————————————————————————————+——————————————+
|           Optional.isPresent()                                   | true | false |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElse(buyNewExpensivePhone())          |   X  |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElseGet(() -> buyNewExpensivePhone()) |      |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+

Quand optional.isPresent() == false, il n'y a pas de différence entre deux façons. Cependant, lorsque optional.isPresent() == true, orElse()appelle toujours la fonction suivante, que vous le vouliez ou non.

Enfin, le cas de test utilisé est le suivant:

Résultat:

------------- Scenario 1 - orElse() --------------------
  1.1. Optional.isPresent() == true
    Going to a very far store to buy a new expensive phone
    Used phone: MyCheapPhone

  1.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

------------- Scenario 2 - orElseGet() --------------------
  2.1. Optional.isPresent() == true
    Used phone: MyCheapPhone

  2.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

Code:

public class TestOptional {
    public Optional<String> findMyPhone(int phoneId) {
        return phoneId == 10
                ? Optional.of("MyCheapPhone")
                : Optional.empty();
    }

    public String buyNewExpensivePhone() {
        System.out.println("\tGoing to a very far store to buy a new expensive phone");
        return "NewExpensivePhone";
    }


    public static void main(String[] args) {
        TestOptional test = new TestOptional();
        String phone;
        System.out.println("------------- Scenario 1 - orElse() --------------------");
        System.out.println("  1.1. Optional.isPresent() == true");
        phone = test.findMyPhone(10).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  1.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("------------- Scenario 2 - orElseGet() --------------------");
        System.out.println("  2.1. Optional.isPresent() == true");
        // Can be written as test::buyNewExpensivePhone
        phone = test.findMyPhone(10).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  2.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");
    }
}

Je pense que vous avez peut-être une erreur dans votre image, elle devrait indiquer "orElseGet" à droite? A part ça, excellent exemple.
Yalla T.

Oui vous avez raison. Merci :) Je le
mettrai à

Pour le deuxième point, semble devrait être à la Optional.isPresent() == falseplace (faux, pas vrai)
Manuel Jordan

Excellent exemple - mais je ne comprends vraiment pas comment les Javadocs pour Optional.orElselesquels les états If a value is present, returns the value, otherwise returns otherpeuvent impliquer ce comportement ...
Erik Finnman

Sur la base de votre explication, pour moi, cela ressemble à un orElse()comportement similaire à celui finallyde l' try-catchexpression. Ai-je raison?
Mike B.

63

Je suis arrivé ici pour le problème mentionné par Kudo .

Je partage mon expérience pour les autres.

orElse, ou orElseGet, telle est la question:

static String B() {
    System.out.println("B()...");
    return "B";
}

public static void main(final String... args) {
    System.out.println(Optional.of("A").orElse(B()));
    System.out.println(Optional.of("A").orElseGet(() -> B()));
}

impressions

B()...
A
A

orElseévalue la valeur de B () de manière interdépendante de la valeur de l'option. Ainsi, orElseGetest paresseux.


7
Ce n'est pas un problème'. C'est le simple fait que l'argument d'une méthode est évalué avant l'exécution de la méthode. Si vous passez B()à une méthode nommée orElse()ou que abc()cela ne fait aucune différence, B()est évalué.
jbx

11
Le problème ici est vraiment la dénomination des méthodes. Le orpréfixe induit les développeurs (y compris moi-même lorsque j'ai posé la question) en pensant qu'il s'agit d'une opération de court-circuit, car c'est ce à quoi nous sommes habitués dans des conditions booléennes. Cependant, ce n'est pas le cas, c'est juste un nom de méthode qui a ordans son préfixe, donc ses arguments seront évalués, qu'il Optionalporte ou non une valeur. Il est regrettable que la dénomination soit source de confusion, pas que nous puissions y faire quoi que ce soit.
jbx

37

Je dirais que la plus grande différence entre orElseet orElseGetvient lorsque nous voulons évaluer quelque chose pour obtenir la nouvelle valeur dans la elsecondition.

Considérez cet exemple simple -

// oldValue is String type field that can be NULL
String value;
if (oldValue != null) {
    value = oldValue;
} else {
    value = apicall().value;
}

Transformons maintenant l'exemple ci-dessus en utilisant Optionalavec orElse,

// oldValue is Optional type field
String value = oldValue.orElse(apicall().value);

Transformons maintenant l'exemple ci-dessus en utilisant Optionalavec orElseGet,

// oldValue is Optional type field
String value = oldValue.orElseGet(() -> apicall().value);

Lorsque orElseest invoqué, le apicall().valueest évalué et transmis à la méthode. Alors que, dans le cas de orElseGetl'évaluation, il ne se produit que si le oldValueest vide. orElseGetpermet une évaluation paresseuse.


4
J'ai perdu beaucoup de temps à cause de ce comportement "étrange" de ifElse (). Je dirais qu'il est logique de préférer ifElseGet () à ifElse ()
Enrico Giurin

3

L'exemple suivant doit démontrer la différence:

String destroyTheWorld() {
  // destroy the world logic
  return "successfully destroyed the world";
}

Optional<String> opt = Optional.empty();

// we're dead
opt.orElse(destroyTheWorld());

// we're safe    
opt.orElseGet(() -> destroyTheWorld());

La réponse apparaît également dans les documents.

public T orElseGet(Supplier<? extends T> other):

Renvoie la valeur si elle est présente, sinon invoquez other et retournez le résultat de cette invocation.

Le Supplier ne sera pas invoqué si les Optionalcadeaux. tandis que,

public T orElse(T other):

Retourne la valeur si elle est présente, sinon retourne autre.

Si otherest une méthode qui renvoie une chaîne, elle sera invoquée, mais sa valeur ne sera pas retournée au cas où elle Optionalexiste.


3

La différence est assez subtile et si vous n'y prêtez pas beaucoup attention, vous continuerez à l'utiliser de manière incorrecte.

La meilleure façon de comprendre la différence entre orElse()et orElseGet()est qu'elle orElse()sera toujours exécutée si la valeur Optional<T>est nulle ou non , mais orElseGet()ne sera exécutée que lorsque la valeur Optional<T>est nulle .

La signification du dictionnaire de orElse est : - exécutez la partie quand quelque chose n'est pas présent, mais ici cela contredit, voir l'exemple ci-dessous:

    Optional<String> nonEmptyOptional = Optional.of("Vishwa Ratna");
    String value = nonEmptyOptional.orElse(iAmStillExecuted());

    public static String iAmStillExecuted(){
    System.out.println("nonEmptyOptional is not NULL,still I am being executed");
    return "I got executed";
    }

Sortie: nonEmptyOptional n'est pas NULL, je suis toujours en cours d'exécution


    Optional<String> emptyOptional = Optional.ofNullable(null);
    String value = emptyOptional.orElse(iAmStillExecuted());
    public static String iAmStillExecuted(){
    System.out.println("emptyOptional is NULL, I am being executed, it is normal as 
    per dictionary");
    return "I got executed";
    }

Sortie : emptyOptional est NULL, je suis en cours d'exécution, c'est normal selon le dictionnaire

Pour orElseGet(), La méthode va selon la signification du dictionnaire, La orElseGet()partie sera exécutée uniquement lorsque l'Optional est nul .

Repères :

+--------------------+------+-----+------------+-------------+-------+
| Benchmark          | Mode | Cnt | Score      | Error       | Units |
+--------------------+------+-----+------------+-------------+-------+
| orElseBenchmark    | avgt | 20  | 60934.425  | ± 15115.599 | ns/op |
+--------------------+------+-----+------------+-------------+-------+
| orElseGetBenchmark | avgt | 20  | 3.798      | ± 0.030     | ns/op |
+--------------------+------+-----+------------+-------------+-------+

Remarques : orElseGet()a clairement surperformé orElse()pour notre exemple particulier.

J'espère que cela efface les doutes de personnes comme moi qui veulent l'exemple fondamental de base :)


2

Vérifiez tout d'abord la déclaration des deux méthodes.

1) OrElse: exécuter la logique et passer le résultat en argument.

public T orElse(T other) {    
 return value != null ? value : other;
}

2) OrElseGet: Exécute la logique si la valeur à l'intérieur de l'option est nulle

public T orElseGet(Supplier<? extends T> other) {
  return value != null ? value : other.get(); 
}

Quelques explications sur la déclaration ci-dessus: L'argument de "Optional.orElse" est toujours exécuté quelle que soit la valeur de l'objet en option (null, vide ou avec valeur). Tenez toujours compte du point mentionné ci-dessus lorsque vous utilisez «Optional.orElse», sinon l'utilisation de «Optional.orElse» peut être très risquée dans la situation suivante.

Risque-1) Problème de journalisation: si le contenu dans orElse contient une instruction de journal: dans ce cas, vous finirez par le journaliser à chaque fois.

Optional.of(getModel())
   .map(x -> {
      //some logic
   })
  .orElse(getDefaultAndLogError());

getDefaultAndLogError() {
  log.error("No Data found, Returning default");
  return defaultValue;
}

Risque-2) Problème de performances: si le contenu dans orElse est chronophage : le contenu chronophage peut être n'importe quel appel de base de données d'opérations d'E / S, appel d'API, lecture de fichier. Si nous mettons un tel contenu dans orElse (), le système finira par exécuter un code inutile.

Optional.of(getModel())
   .map(x -> //some logic)
   .orElse(getDefaultFromDb());

getDefaultFromDb() {
   return dataBaseServe.getDefaultValue(); //api call, db call.
}

Risque-3) Problème d'état ou de bogue illégal: Si le contenu dans orElse mute un état d'objet: nous pourrions utiliser le même objet à un autre endroit, disons à l'intérieur de la fonction Optional.map et cela peut nous mettre dans un bogue critique.

List<Model> list = new ArrayList<>();
Optional.of(getModel())
  .map(x -> {
  })
  .orElse(get(list));

get(List < String > list) {
   log.error("No Data found, Returning default");
   list.add(defaultValue);
   return defaultValue;
}

Alors, quand peut-on aller avec orElse ()? Préférez utiliser orElse lorsque la valeur par défaut est un objet constant, enum. Dans tous les cas ci-dessus, nous pouvons utiliser Optional.orElseGet () (qui ne s'exécute que lorsque Optional contient une valeur non vide) au lieu de Optional.orElse (). Pourquoi?? Dans orElse, nous transmettons la valeur de résultat par défaut, mais dans orElseGet, nous transmettons Supplier et la méthode de Supplier ne s'exécute que si la valeur dans Optional est nulle.

Points clés à retenir de ceci:

  1. N'utilisez pas «Optional.orElse» s'il contient une instruction de journal.
  2. N'utilisez pas «Optional.orElse» s'il contient une logique chronophage.
  3. N'utilisez pas «Optional.orElse» s'il mute un état d'objet.
  4. Utilisez «Optional.orElse» si nous devons renvoyer une constante, enum.
  5. Préférez «Optional.orElseGet» dans les situations mentionnées aux points 1,2 et 3.

Je l'ai expliqué au point-2 ( "Optional.map/Optional.orElse"! = "If / else" ) mon blog moyen. Utiliser Java8 comme programmeur et non comme codeur


0

Considérant le code suivant:

import java.util.Optional;

// one class needs to have a main() method
public class Test
{
  public String orelesMethod() {
    System.out.println("in the Method");
    return "hello";
  }

  public void test() {
    String value;
    value = Optional.<String>ofNullable("test").orElseGet(this::orelesMethod);
    System.out.println(value); 

    value = Optional.<String>ofNullable("test").orElse(orelesMethod());
    System.out.println(value); 
  }

  // arguments are passed using the text field below this editor
  public static void main(String[] args)
  {
    Test test = new Test();

    test.test();
  }
}

si nous obtenons valuede cette façon: Optional.<String>ofNullable(null), il n'y a pas de différence entre orElseGet () et OrElse (), mais si nous obtenons valuede cette façon: Optional.<String>ofNullable("test"), orelesMethod()en orElseGet()ne sera pas appelé mais orElse()il sera appelé

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.