Różnica między `Optional.orElse ()` i `Optional.orElseGet ()`

206

Próbuję zrozumieć różnicę między metodami Optional<T>.orElse()a Optional<T>.orElseGet().

Opis orElse()metody to „Zwróć wartość, jeśli jest obecna, w przeciwnym razie zwróć inną”.

Chociaż opis orElseGet()metody brzmi „Zwróć wartość, jeśli jest obecna, w przeciwnym razie wywołaj inną i zwróć wynik tego wywołania”.

orElseGet()Metoda zajmuje interfejs funkcjonalny dostawca, który zasadniczo nie bierze żadnych parametrów i powrót T.

W jakiej sytuacji musiałbyś użyć orElseGet()? Jeśli masz metodę, T myDefault()dlaczego optional.orElse(myDefault())raczej tego nie zrobiłbyś optional.orElseGet(() -> myDefault())?

Nie wydaje się, żeby orElseGet()opóźniało wykonanie wyrażenia lambda na jakiś czas czy coś, więc po co to ma sens? (Myślałbym, że byłoby bardziej użyteczne, gdyby zwrócił bezpieczniejszego, Optional<T>którego get()nigdy nie rzuca a NoSuchElementExceptioni isPresent()zawsze zwraca prawdę ... ale oczywiście nie jest, po prostu zwraca Tjak orElse()).

Czy brakuje mi innej różnicy?

jbx
źródło
7
Powodem jest to, że kiedy orElseGetgo używasz , dzwoni do dostawcy tylko w przypadku braku wartości.
Alex Salauyou
9
Ach, rozumiem. Tak więc w przypadku orElse()tej myDefault()metody jest nadal nazywa, ale jego wartość jest nie tylko zwrot używany.
jbx
3
Pytanie wzięte pod uwagę, ponieważ z tego, co widziałem, nieporozumienie lub po prostu zapominanie o użyciu orElseGet()może spowodować poważne błędy: medium.com/alphadev-
thinkts/…
Dobre wyjaśnienie można znaleźć tutaj: baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

Odpowiedzi:

172

Weź te dwa scenariusze:

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

Jeśli optnie zawiera wartości, oba są rzeczywiście równoważne. Ale jeśli opt nie zawierają wartości, ile Fooobiekty będą tworzone?

Ps: oczywiście w tym przykładzie różnica prawdopodobnie nie byłaby mierzalna, ale jeśli musisz uzyskać domyślną wartość np. Ze zdalnej usługi internetowej lub z bazy danych, nagle staje się ona bardzo ważna.

biziclop
źródło
22
Dzięki za wyjaśnienia chłopaki. Różnica jest więc subtelna, ale znacząca. W drugim przypadku nie utworzy nowego Fooobiektu, podczas gdy w pierwszym przypadku go utworzy, ale nie użyje go, jeśli wewnątrz jest wartość Optional.
jbx
5
@jbx Tak, a w moim przytoczonym przykładzie prawdopodobnie nie ma to żadnej różnicy, ale jeśli musisz uzyskać domyślną wartość np. ze zdalnej usługi internetowej lub bazy danych, różnica nagle staje się bardzo ważna.
biziclop
2
@jbx: mieszacie dwie rzeczy. Istnieją już pytania na temat SO dotyczące dziwnych wyników testów, które były po prostu spowodowane nieużywaniem wyniku obliczeń. JVM może to zrobić. Z drugiej strony, System.out.println()to nie kalkulacja ale oświadczenie wytwarzania zaobserwować efekt uboczny. Powiedziałem już, że obserwowane skutki uboczne będą utrudniać optymalizację (strumień wyjściowy konsoli jest zasobem zewnętrznym).
Holger
7
Po raz pierwszy widzę pytanie zamiast odpowiedzi zostało zaakceptowane.
Kirill G.
4
jeśli na przykład musisz uzyskać wartość domyślną ze zdalnej usługi internetowej ”, to był dokładnie mój scenariusz. W moim przypadku opcjonalnym było zapytanie, a domyślnym w przypadku braku zapytania było pobranie wszystkich wartości ... tak, lub ElseGet skrócił czas działania tej operacji o 1000 razy.
scottysseus
109

Krótka odpowiedź:

  • OrElse () zawsze będzie zadzwonić do danej funkcji, czy chcesz, czy nie, niezależnie od Optional.isPresent()wartości
  • orElseGet () wywoła daną funkcję tylko wtedy, gdyOptional.isPresent() == false

W prawdziwym kodzie warto rozważyć drugie podejście, gdy uzyskanie wymaganego zasobu jest drogie .

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

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

Aby uzyskać więcej informacji, rozważ następujący przykład z tą funkcją:

public Optional<String> findMyPhone(int phoneId)

Różnica jest następująca:

                           X : buyNewExpensivePhone() called

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

Kiedy optional.isPresent() == falsenie ma różnicy między dwoma sposobami. Jednak kiedy optional.isPresent() == true, orElse()zawsze wywołuje następną funkcję, czy tego chcesz, czy nie.

Wreszcie zastosowany przypadek testowy jest następujący:

Wynik:

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

Kod:

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");
    }
}
nxhoaf
źródło
Wydaje mi się, że na obrazie występuje błąd. Po prawej stronie powinien znajdować się komunikat „orElseGet”? Poza tym świetny przykład.
Yalla T.
Tak, masz rację. Dziękuję :) Zaktualizuję go za kilka godzin
nxhoaf,
Drugi punkt wydaje się Optional.isPresent() == falsezamiast tego (fałszywy, nieprawdziwy)
Manuel Jordan
Świetny przykład - ale tak naprawdę nie rozumiem, w jaki sposób Javadocs dla Optional.orElsektórych stanów If a value is present, returns the value, otherwise returns othermogą sugerować takie zachowanie ...
Erik Finnman,
W oparciu o wyjaśnienia, dla mnie to wygląda na to, że orElse()zachowuje się podobnie do finallyw try-catchwypowiedzi. Mam rację?
Mike B.
63

Sięgnąłem tutaj po problem, o którym wspomniał Kudo .

Dzielę się moim doświadczeniem z innymi.

orElselub orElseGettakie jest pytanie:

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

odciski

B()...
A
A

orElseocenia wartość B () niezależnie od wartości opcjonalnej. Jest więc orElseGetleniwy.

Jin Kwon
źródło
7
To nie jest problem'. Jest to prosty fakt, że argument dla metody jest oceniany przed wykonaniem metody. Jeśli przejdziesz B()do metody o nazwie orElse()lub abc()nie robi to żadnej różnicy, B()zostanie oceniony.
jbx
11
Problemem tutaj jest tak naprawdę nazywanie metod. Te ordeweloperzy błędnego prefiksu (w tym ja, kiedy poprosiłem problemu) do myślenia, że jest to operacja zwarcie, ponieważ to, co jesteśmy przyzwyczajeni w logicznych warunków. Jednak tak nie jest, to tylko nazwa metody, która ma orw swoim prefiksie, więc jej argumenty zostaną ocenione, niezależnie od tego, czy Optionalma wartość, czy nie. Szkoda, że ​​nazewnictwo jest mylące, nie dlatego, że nic na to nie poradzimy.
jbx,
37

Powiedziałbym, że największa różnica między orElsei orElseGetpojawia się, gdy chcemy coś ocenić, aby uzyskać nową wartość w tym elsestanie.

Rozważ ten prosty przykład -

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

Teraz przekształcmy powyższy przykład na użycie Optionalwraz z orElse,

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

Teraz przekształcmy powyższy przykład na użycie Optionalwraz z orElseGet,

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

Po orElsewywołaniu apicall().valuejest on oceniany i przekazywany do metody. Natomiast w przypadku orElseGetoceny dzieje się tylko wtedy, gdy oldValuejest pusta. orElseGetumożliwia leniwą ocenę.

devang
źródło
4
Zmarnowałem wiele razy z powodu tego „dziwnego” zachowania ifElse (). Powiedziałbym, że sensownie jest preferować ifElseGet () niż ifElse ()
Enrico Giurin
3

Poniższy przykład powinien wykazać różnicę:

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

Odpowiedź pojawia się również w dokumentach.

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

Zwróć wartość, jeśli jest obecna, w przeciwnym razie wywołaj inną i zwróć wynik tego wywołania.

Nie Supplier będą wywoływane, jeśli Optionalprezenty. natomiast,

public T orElse(T other):

Zwróć wartość, jeśli jest obecna, w przeciwnym razie zwróć inną.

Jeśli othermetoda zwraca łańcuch, zostanie on wywołany, ale jego wartość nie zostanie zwrócona, jeśli Optionalistnieje.

Maroun
źródło
3

Różnica jest dość subtelna i jeśli nie zwrócisz na to większej uwagi, będziesz używać jej w niewłaściwy sposób.

Najlepszym sposobem na zrozumienie różnicy między orElse()i orElseGet()jest to, że orElse()zawsze będzie wykonane, jeśli wartośćOptional<T> jest zerowa, czy nie , ale orElseGet()zostanie wykonana tylko, gdy wartośćOptional<T> będzie zerowa .

Słownikowe znaczenie orElse to : - wykonaj część, gdy czegoś nie ma, ale tutaj jest to sprzeczne, patrz poniższy przykład:

    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";
    }

Dane wyjściowe: nonEmptyOptional nie ma wartości NULL, wciąż jestem wykonywany


    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";
    }

Wyjście : emptyOptional ma wartość NULL, jestem wykonywany, jest to normalne jak na słownik

Ponieważ orElseGet()metoda orElseGet()działa zgodnie ze znaczeniem słownikowym, część zostanie wykonana tylko wtedy, gdy opcja jest pusta .

Benchmarki :

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

Uwagi : orElseGet()wyraźnie wyprzedził orElse()nasz konkretny przykład.

Mam nadzieję, że rozwiąże to wątpliwości ludzi takich jak ja, którzy chcą bardzo podstawowego przykładu z ziemi :)

Vishwa Ratna
źródło
2

Przede wszystkim sprawdź deklarację obu metod.

1) OrElse: Wykonaj logikę i przekaż wynik jako argument.

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

2) OrElseGet: Wykonaj logikę, jeśli wartość wewnątrz opcjonalnego jest pusta

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

Kilka wyjaśnień na temat powyższej deklaracji: Argument „Optional.orElse” zawsze jest wykonywany niezależnie od wartości obiektu w postaci opcjonalnej (null, pusta lub z wartością). Podczas korzystania z „Optional.orElse” zawsze miej na uwadze powyższy punkt, w przeciwnym razie użycie „Optional.orElse” może być bardzo ryzykowne w następującej sytuacji.

Ryzyko-1) Problem z logowaniem: Jeśli zawartość wewnątrz orElse zawiera jakieś oświadczenie dziennika: W takim przypadku będziesz go logować za każdym razem.

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

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

Ryzyko-2) Problem z wydajnością: jeśli zawartość wewnątrz orElse jest czasochłonna: treściochłonne mogą być dowolne operacje we / wy wywołanie DB, wywołanie API, odczyt pliku. Jeśli umieścimy taką treść w orElse (), system ostatecznie wykona kod bezużyteczny.

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

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

Ryzyko 3) Nielegalny stan lub błąd: jeśli zawartość wewnątrz orElse mutuje jakiś stan obiektu: Być może używamy tego samego obiektu w innym miejscu, powiedzmy w funkcji Optional.map i może to spowodować błąd krytyczny.

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

Zatem, kiedy możemy przejść z orElse ()? Preferuj użycie orElse, gdy wartością domyślną jest jakiś stały obiekt, wyliczanie. We wszystkich powyższych przypadkach możemy korzystać z Optional.orElseGet () (który działa tylko wtedy, gdy Optional zawiera niepustą wartość) zamiast Optional.orElse (). Czemu?? W orElse przekazujemy domyślną wartość wyniku, ale w orElseGet przekazujemy Dostawcę, a metoda Dostawcy jest wykonywana tylko wtedy, gdy wartość w Opcjonalnym jest równa null.

Najważniejsze rzeczy z tego:

  1. Nie używaj „Optional.orElse”, jeśli zawiera ona jakąkolwiek instrukcję dziennika.
  2. Nie używaj „Optional.orElse”, jeśli zawiera logikę czasochłonną.
  3. Nie używaj „Optional.orElse”, jeśli powoduje to mutację stanu niektórych obiektów.
  4. Użyj „Optional.orElse”, jeśli musimy zwrócić stałą, wyliczanie.
  5. Preferuj „Optional.orElseGet” w sytuacjach wymienionych w punktach 1,2 i 3.

Wyjaśniłem to w punkcie-2 ( „Optional.map/Optional.orElse”! = „If / else” ) mój średni blog. Używaj Java8 jako programisty, a nie kodera

Piyush N.
źródło
0

Biorąc pod uwagę następujący kod:

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

jeśli uda nam valuesię w ten sposób: Optional.<String>ofNullable(null)nie ma żadnej różnicy między orElseGet () i OrElse (), ale jeśli uda nam valuesię w ten sposób: Optional.<String>ofNullable("test"), orelesMethod()w orElseGet()nie zostanie wywołana, ale w orElse()nim będzie nazwane

ratzip
źródło