Unterschied zwischen "Optional.orElse ()" und "Optional.orElseGet ()"


206

Ich versuche den Unterschied zwischen Optional<T>.orElse()und Optional<T>.orElseGet()Methoden zu verstehen .

Die Beschreibung für die orElse()Methode lautet "Geben Sie den Wert zurück, falls vorhanden, andernfalls geben Sie einen anderen zurück."

Die Beschreibung für die orElseGet()Methode lautet "Geben Sie den Wert zurück, falls vorhanden, rufen Sie andernfalls den anderen auf und geben Sie das Ergebnis dieses Aufrufs zurück."

Die orElseGet()Methode verwendet eine Lieferantenfunktionsschnittstelle, die im Wesentlichen keine Parameter akzeptiert und zurückgibt T.

In welcher Situation müssten Sie verwenden orElseGet()? Wenn Sie eine Methode haben, T myDefault()warum würden Sie das nicht einfach tun optional.orElse(myDefault())als optional.orElseGet(() -> myDefault())?

Es scheint nicht so, orElseGet()als würde die Ausführung des Lambda-Ausdrucks auf einen späteren Zeitpunkt verschoben oder so. Worum geht es also? (Ich hätte gedacht , dass es sinnvoller wäre , wenn es zu einem sichereren zurückgegeben , Optional<T>die get()nie wirft NoSuchElementExceptionund isPresent()immer wieder wahr ... aber offenbar ist es nicht, es gerade Rückkehr Twie orElse()).

Gibt es einen anderen Unterschied, den ich vermisse?


7
Der Grund ist, wenn Sie es verwenden orElseGet, ruft der Lieferant nur an, wenn kein Wert vorhanden ist.
Alex Salauyou

9
Ah ok verstanden. Im Falle orElse()der myDefault()Methode wird also immer noch aufgerufen, aber ihr Rückgabewert wird einfach nicht verwendet.
jbx

3
Upvoted Frage, weil nach dem, was ich Missverständnisse oder einfach das Vergessen gesehen habe, zu orElseGet()einigen schwerwiegenden Fehlern führen kann: medium.com/alphadev-
Thoughts

Antworten:


172

Nehmen Sie diese beiden Szenarien:

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

Wenn optkein Wert enthalten ist, sind die beiden tatsächlich gleichwertig. Aber wenn opt es einen Wert enthält, wie viele FooObjekte werden erstellt?

Ps: Natürlich wäre in diesem Beispiel der Unterschied wahrscheinlich nicht messbar, aber wenn Sie Ihren Standardwert beispielsweise von einem Remote-Webdienst oder von einer Datenbank beziehen müssen, wird er plötzlich sehr wichtig.


22
Danke für die Klarstellung Jungs. Der Unterschied ist also subtil, aber signifikant. Im zweiten Fall wird kein neues FooObjekt erstellt, im ersten Fall wird es erstellt, es wird jedoch nicht verwendet, wenn sich ein Wert in der befindet Optional.
jbx

5
@jbx Ja, und in meinem Noddy-Beispiel macht es wohl keinen wirklichen Unterschied, aber wenn Sie Ihren Standardwert beispielsweise von einem Remote-Webdienst oder von einer Datenbank beziehen müssen, wird der Unterschied plötzlich sehr wichtig.
Biziclop

2
@jbx: Sie vermischen zwei Dinge. Es gibt bereits Fragen zu SO bezüglich seltsamer Benchmark-Ergebnisse, die einfach dadurch verursacht wurden, dass das Ergebnis einer Berechnung nicht verwendet wurde. Die JVM kann das tun. Auf der anderen Seite System.out.println()handelt es sich nicht um eine Berechnung, sondern um eine Aussage, die einen beobachtbaren Nebeneffekt hervorruft. Und ich habe bereits gesagt, dass beobachtbare Nebenwirkungen Optimierungen behindern (der Konsolenausgabestream ist eine externe Ressource).
Holger

7
Das ist das erste Mal, dass ich sehe, dass eine Frage anstelle einer Antwort akzeptiert wurde.
Kirill G.

4
" Wenn Sie Ihren Standardwert beispielsweise von einem Remote-Webdienst abrufen müssen ", war dies genau mein Szenario. In meinem Fall war das Optionale eine Abfrage, und bei Fehlen einer Abfrage wurde standardmäßig alle Werte abgerufen ... Ja, oder ElseGet hat die Laufzeit dieses Vorgangs um das 1000-fache reduziert.
Scottysseus

109

Kurze Antwort:

  • OrElse () wird die gegebene Funktion immer anrufen , ob Sie es wollen oder nicht, unabhängig von Optional.isPresent()Wert
  • orElseGet () ruft die angegebene Funktion nur auf, wenn dieOptional.isPresent() == false

In echtem Code sollten Sie den zweiten Ansatz in Betracht ziehen, wenn die Beschaffung der erforderlichen Ressource teuer ist .

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

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

Weitere Details finden Sie im folgenden Beispiel mit dieser Funktion:

public Optional<String> findMyPhone(int phoneId)

Der Unterschied ist wie folgt:

                           X : buyNewExpensivePhone() called

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

Wann optional.isPresent() == falsegibt es keinen Unterschied zwischen zwei Möglichkeiten. Wenn jedoch optional.isPresent() == true, orElse()ruft immer die nachfolgende Funktion auf, ob Sie es wollen oder nicht.

Schließlich wird der folgende Testfall verwendet:

Ergebnis:

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

Ich denke, Sie haben möglicherweise einen Fehler in Ihrem Bild. Auf der rechten Seite sollte "orElseGet" stehen. Ansonsten tolles Beispiel.
Yalla T.

Ja du hast Recht. Vielen Dank :) Ich werde es in den nächsten Stunden
aktualisieren

Für den zweiten Punkt sollte Optional.isPresent() == falsestattdessen (falsch, nicht wahr) sein
Manuel Jordan

Tolles Beispiel - aber ich verstehe wirklich nicht, wie die Javadocs, für Optional.orElsedie Staaten If a value is present, returns the value, otherwise returns otherdieses Verhalten implizieren können ...
Erik Finnman

Basierend auf Ihrer Erklärung sieht es für mich so aus, als würde orElse()sich das ähnlich wie finallyim try-catchAusdruck verhalten . Hab ich recht?
Mike B.

63

Ich griff hier nach dem Problem, das Kudo erwähnte.

Ich teile meine Erfahrungen für andere.

orElseoder orElseGetdas ist die Frage:

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

druckt

B()...
A
A

orElsewertet den Wert von B () in Abhängigkeit vom Wert des optionalen aus. Also orElseGetist faul.


7
Es ist kein Problem'. Es ist die einfache Tatsache, dass das Argument für eine Methode vor der Ausführung der Methode ausgewertet wird. Wenn Sie B()an eine Methode mit dem Namen übergeben orElse()oder abc()diese keinen Unterschied macht, B()wird sie ausgewertet.
jbx

11
Das Problem hier ist wirklich die Benennung der Methoden. Das orPräfix führt Entwickler (einschließlich meiner selbst, als ich das Problem stellte) in die Irre, zu denken, dass es sich um eine Kurzschlussoperation handelt, da wir dies unter booleschen Bedingungen gewohnt sind. Es ist jedoch nicht so, dass es sich nur um einen Methodennamen handelt, ordessen Präfix Optionallautet. Daher werden seine Argumente ausgewertet, unabhängig davon, ob sie einen Wert enthalten oder nicht. Es ist bedauerlich, dass die Benennung verwirrend ist und wir nichts dagegen tun können.
jbx

37

Ich würde sagen, der größte Unterschied zwischen orElseund orElseGetkommt, wenn wir etwas bewerten wollen, um den neuen Wert in der elseBedingung zu erhalten.

Betrachten Sie dieses einfache Beispiel -

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

Lassen Sie uns nun das obige Beispiel in die Verwendung Optionalmit orElse, umwandeln.

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

Lassen Sie uns nun das obige Beispiel in die Verwendung Optionalmit orElseGet, umwandeln.

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

Beim orElseAufrufen wird das apicall().valueausgewertet und an die Methode übergeben. Während bei orElseGetder Auswertung nur, wenn die oldValueleer ist. orElseGetermöglicht eine verzögerte Auswertung.


4
Ich habe viel Zeit wegen dieses "seltsamen" Verhaltens von ifElse () verschwendet. Ich würde sagen, es ist sinnvoll, ifElseGet () gegenüber ifElse ()
vorzuziehen

3

Das folgende Beispiel soll den Unterschied demonstrieren:

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

Die Antwort erscheint auch in den Dokumenten.

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

Geben Sie den Wert zurück, falls vorhanden, andernfalls rufen Sie other auf und geben Sie das Ergebnis dieses Aufrufs zurück.

Das Supplier wird nicht aufgerufen, wenn die OptionalGeschenke. wohingegen,

public T orElse(T other)::

Geben Sie den Wert zurück, falls vorhanden, andernfalls einen anderen.

Wenn otheres sich um eine Methode handelt, die eine Zeichenfolge zurückgibt, wird diese aufgerufen, ihr Wert wird jedoch nicht zurückgegeben, falls die Zeichenfolge Optionalvorhanden ist.


3

Der Unterschied ist ziemlich subtil und wenn Sie nicht viel Aufmerksamkeit schenken, werden Sie es falsch verwenden.

Der beste Weg , den Unterschied zwischen verstehen orElse()und orElseGet()wird , dass orElse()wird immer dann ausgeführt werden , wenn der Optional<T>ist null oder nicht , aber orElseGet()wird nur dann ausgeführt werden , wenn Optional<T>ist null .

Die Wörterbuchbedeutung von orElse ist : - den Teil aus, wenn etwas nicht vorhanden ist, aber hier widerspricht es, siehe das folgende Beispiel:

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

Ausgabe: nonEmptyOptional ist nicht NULL, ich werde immer noch ausgeführt


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

Ausgabe : emptyOptional ist NULL, ich werde ausgeführt, es ist normal gemäß Wörterbuch

Denn orElseGet()die Methode entspricht der Wörterbuchbedeutung orElseGet() wird Teil nur dann ausgeführt werden , wenn die optionalen ist null .

Benchmarks :

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

Anmerkungen : orElseGet() hat sich orElse()für unser spezielles Beispiel deutlich übertroffen .

Hoffe, es klärt die Zweifel von Leuten wie mir, die das grundlegende Beispiel wollen :)


2

Überprüfen Sie zunächst die Deklaration beider Methoden.

1) OrElse: Führen Sie die Logik aus und übergeben Sie das Ergebnis als Argument.

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

2) OrElseGet: Führen Sie die Logik aus, wenn der Wert im optionalen Wert null ist

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

Einige Erklärungen zur obigen Erklärung: Das Argument "Optional.orElse" wird immer ausgeführt, unabhängig vom Wert des Objekts in optional (null, leer oder mit Wert). Beachten Sie bei der Verwendung von „Optional.orElse“ immer den oben genannten Punkt. Andernfalls kann die Verwendung von „Optional.orElse“ in der folgenden Situation sehr riskant sein.

Risiko-1) Protokollierungsproblem: Wenn der Inhalt in orElse eine Protokollanweisung enthält: In diesem Fall werden Sie ihn jedes Mal protokollieren.

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

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

Risiko-2) Leistungsproblem: Wenn der Inhalt in orElse zeitintensiv ist: Zeitintensiver Inhalt kann ein beliebiger DB-Aufruf für E / A-Vorgänge, ein API-Aufruf oder das Lesen von Dateien sein. Wenn wir solchen Inhalt in orElse () einfügen, führt das System einen Code aus, der keinen Nutzen hat.

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

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

Risiko-3) Unzulässiger Status oder Fehler Problem : Wenn der Inhalt in orElse einen Objektstatus mutiert: Wir verwenden möglicherweise dasselbe Objekt an einer anderen Stelle, beispielsweise in der Funktion Optional.map, und es kann zu einem kritischen Fehler kommen.

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

Wann können wir dann mit orElse () gehen? Verwenden Sie lieber orElse, wenn der Standardwert ein konstantes Objekt ist, enum. In allen oben genannten Fällen können wir Optional.orElseGet () (das nur ausgeführt wird, wenn Optional einen nicht leeren Wert enthält) anstelle von Optional.orElse () verwenden. Warum?? In orElse übergeben wir den Standardergebniswert, in orElseGet übergeben wir Supplier, und die Methode des Supplier wird nur ausgeführt, wenn der Wert in Optional null ist.

Wichtige Erkenntnisse daraus:

  1. Verwenden Sie "Optional.orElse" nicht, wenn es eine Protokollanweisung enthält.
  2. Verwenden Sie "Optional.orElse" nicht, wenn es zeitintensive Logik enthält.
  3. Verwenden Sie "Optional.orElse" nicht, wenn ein Objektstatus mutiert wird.
  4. Verwenden Sie "Optional.orElse", wenn wir eine Konstante, enum, zurückgeben müssen.
  5. Bevorzugen Sie "Optional.orElseGet" in den in den Punkten 1,2 und 3 genannten Situationen.

Ich habe dies in Punkt 2 ( "Optional.map/Optional.orElse"! = "If / else" ) in meinem mittleren Blog erklärt. Verwenden Sie Java8 als Programmierer und nicht als Codierer


0

Berücksichtigt man den folgenden Code:

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

wenn wir bekommen valueauf diese Weise: Optional.<String>ofNullable(null)Es gibt keinen Unterschied zwischen orElseGet () und OrElse (), aber wenn wir bekommen valueauf diese Weise: Optional.<String>ofNullable("test"), orelesMethod()in orElseGet()nicht in Anspruch genommen werden , aber in orElse()es wird aufgerufen,

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.