Differenza tra `Optional.orElse ()` e `Optional.orElseGet ()`


206

Sto cercando di capire la differenza tra i metodi Optional<T>.orElse()e Optional<T>.orElseGet().

La descrizione per il orElse()metodo è "Restituisce il valore se presente, altrimenti restituisce altro".

Mentre, la descrizione per il orElseGet()metodo è "Restituisce il valore se presente, altrimenti invoca altro e restituisce il risultato di quella chiamata."

Il orElseGet()metodo utilizza un'interfaccia funzionale del fornitore, che essenzialmente non accetta parametri e restituisce T.

In quale situazione dovresti usare orElseGet()? Se hai un metodo, T myDefault()perché non dovresti fare optional.orElse(myDefault())piuttosto che optional.orElseGet(() -> myDefault())?

Non sembra che orElseGet()rimandare l'esecuzione dell'espressione lambda a qualche tempo successivo o qualcosa del genere, quindi che senso ha? (Avrei pensato che sarebbe stato più utile se fosse tornato più sicuro di Optional<T>chiget() non lancia mai un NoSuchElementExceptione isPresent()restituisce sempre vero ... ma evidentemente non lo è, ritorna semplicemente TcomeorElse() ).

C'è qualche altra differenza che mi manca?


7
Il motivo è quando lo usi orElseGetchiama il fornitore solo se il valore è assente.
Alex Salauyou,

9
Ah ok capito. Quindi, nel caso del orElse()del myDefault()metodo è ancora chiamato, ma il suo valore di ritorno non è solo utilizzato.
jbx,

3
Domanda votata perché da quello che ho visto incomprensioni o semplicemente dimenticare di usare orElseGet()può provocare alcuni bug gravi: medium.com/alphadev-thoughts/…
softarn

Una buona spiegazione è disponibile qui: baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

Risposte:


172

Prendi questi due scenari:

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

Se optnon contiene un valore, i due sono effettivamente equivalenti. Ma se opt non contenere un valore, quanteFoo oggetti saranno creati?

Ps: ovviamente in questo esempio la differenza probabilmente non sarebbe misurabile, ma se devi ottenere il tuo valore predefinito da un servizio web remoto, o da un database, diventa improvvisamente molto importante.


22
Grazie per il chiarimento ragazzi. Quindi la differenza è sottile ma significativa. Nel secondo caso, non creerà un nuovo Foooggetto, mentre nel primo caso lo creerà, ma non lo userà se c'è un valore all'interno di Optional.
jbx,

5
@jbx Sì, e nel mio noddy esempio probabilmente non fa alcuna differenza reale, ma se devi ottenere il tuo valore predefinito da un servizio web remoto, ad esempio, o da un database, la differenza diventa improvvisamente molto importante.
biziclop,

2
@jbx: stai mescolando due cose. Ci sono già domande su SO riguardanti strani risultati di benchmark che sono stati semplicemente causati dal non utilizzo del risultato di un calcolo. La JVM può farlo. D'altra parte, nonSystem.out.println() è un calcolo ma un'affermazione che produce un effetto collaterale osservabile. E ho già detto che gli effetti collaterali osservabili ostacoleranno le ottimizzazioni (il flusso di output della console è una risorsa esterna).
Holger,

7
È la prima volta che vedo una domanda invece di una risposta accettata.
Kirill G.

4
"Ad esempio, se devi ottenere il valore predefinito da un servizio Web remoto ", questo era esattamente il mio scenario. Nel mio caso, facoltativo era una query e il valore predefinito in assenza di una query era recuperare tutti i valori ... sì, oppure ElseGet ha ridotto il tempo di esecuzione di tale operazione di 1000 volte.
scottysseus,

109

Risposta breve:

  • orElse () chiamerà sempre la funzione specificata, che tu lo voglia o no, indipendentemente daOptional.isPresent() valore
  • orElseGet () chiamerà la funzione specificata solo quandoOptional.isPresent() == false

Nel codice reale, potresti voler considerare il secondo approccio quando la risorsa richiesta è costosa da ottenere .

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

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

Per ulteriori dettagli, considerare il seguente esempio con questa funzione:

public Optional<String> findMyPhone(int phoneId)

La differenza è la seguente:

                           X : buyNewExpensivePhone() called

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

Quando optional.isPresent() == false, non c'è differenza tra due modi. Tuttavia, quando optional.isPresent() == true, orElse()chiama sempre la funzione successiva, che tu lo voglia o no.

Infine, il test case utilizzato è il seguente:

Risultato:

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

Codice:

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

Penso che potresti avere un errore nella tua foto, dovrebbe dire "orElseGet" sulla destra? Oltre a ciò, ottimo esempio.
Yalla T.,

Sì hai ragione. Grazie :) Lo aggiornerò nelle prossime ore
nxhoaf,

Per il secondo punto, sembra Optional.isPresent() == falseinvece essere (falso, non vero)
Manuel Jordan,

Grande esempio - ma non capisco come i Javadocs per Optional.orElsequali stati If a value is present, returns the value, otherwise returns otherpossano implicare questo comportamento ...
Erik Finnman,

Sulla base della sua spiegazione, per me sembra che orElse()si comporta come finallyin try-catchespressione. Ho ragione?
Mike B.

63

Ho raggiunto qui per il problema menzionato da Kudo .

Sto condividendo la mia esperienza per gli altri.

orElseo orElseGet, questa è la domanda:

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

stampe

B()...
A
A

orElsevaluta il valore di B () in modo interdipendente rispetto al valore dell'opzionale. Quindi, orElseGetè pigro.


7
Non è un problema'. È il semplice fatto che l'argomento di un metodo viene valutato prima dell'esecuzione del metodo. Se passi B()a un metodo chiamato orElse()o abc()non fa alcuna differenza, B()viene valutato.
jbx,

11
Il problema qui è davvero la denominazione dei metodi. Il orprefisso induce in errore gli sviluppatori (incluso me stesso quando ho chiesto il problema) a pensare che si tratti di un'operazione di corto circuito, perché è quello a cui siamo abituati in condizioni booleane. Tuttavia, non lo è, è solo un nome di metodo che ha ornel suo prefisso, quindi i suoi argomenti verranno valutati, indipendentemente dal fatto che Optionalstia portando un valore o meno. È un peccato che la denominazione sia confusa, non che possiamo fare qualcosa al riguardo.
jbx

37

Direi la più grande differenza tra orElsee orElseGetarriva quando vogliamo valutare qualcosa per ottenere il nuovo valore nella elsecondizione.

Considera questo semplice esempio:

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

Ora trasformiamo l'esempio sopra in uso Optionalinsieme aorElse,

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

Ora trasformiamo l'esempio sopra in uso Optionalinsieme a orElseGet,

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

Quando orElseviene invocato, apicall().valueviene valutato e passato al metodo. Considerando che, nel caso della orElseGetvalutazione avviene solo se oldValueè vuoto. orElseGetconsente una valutazione pigra.


4
Ho perso un sacco di volte a causa di questo "strano" comportamento di ifElse (). Direi che ha senso preferire ifElseGet () piuttosto che ifElse ()
Enrico Giurin,

3

L'esempio seguente dovrebbe dimostrare la differenza:

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 risposta appare anche nei documenti.

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

Restituisce il valore se presente, altrimenti invoca altro e restituisce il risultato di quella chiamata.

Il Supplier non verrà invocato se i Optionalregali. mentre,

public T orElse(T other):

Restituisce il valore se presente, altrimenti restituisce altro.

Se otherè un metodo che restituisce una stringa, verrà invocata, ma il suo valore non verrà restituito nel caso Optionalesista.


3

La differenza è piuttosto sottile e se non presti molta attenzione, la manterrai in modo errato.

Il modo migliore per capire la differenza tra orElse()e orElseGet()è che orElse()sarà sempre eseguito se Optional<T>è nullo o no , ma orElseGet()sarà eseguito solo quando Optional<T>è nullo .

Il significato del dizionario di orElse è : - esegui la parte quando qualcosa non è presente, ma qui è in contraddizione, vedi l'esempio seguente:

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

Output: nonEmptyOptional non è NULL, tuttavia sto eseguendo


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

Output : emptyOptional è NULL, sto eseguendo, è normale come da dizionario

Per orElseGet(), Il metodo va secondo il significato del dizionario, La orElseGet()parte verrà eseguita solo quando l'Opzionale è nullo .

Benchmark :

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

Osservazioni : orElseGet()ha chiaramente sovraperformato il orElse()nostro esempio particolare.

Spero che chiarisca i dubbi di persone come me che vogliono l'esempio di base molto semplice :)


2

Prima di tutto controlla la dichiarazione di entrambi i metodi.

1) OrElse: esegue la logica e passa il risultato come argomento.

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

2) OrElseGet: esegue la logica se il valore all'interno dell'opzionale è nullo

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

Qualche spiegazione sulla dichiarazione di cui sopra: l'argomento di "Optional.orElse" viene sempre eseguito indipendentemente dal valore dell'oggetto in facoltativo (null, vuoto o con valore). Quando si utilizza "Optional.orElse", tenere presente sempre il punto sopra menzionato, altrimenti l'uso di "Optional.orElse" può essere molto rischioso nella seguente situazione.

Rischio-1) Problema di registrazione: se il contenuto all'interno di orElse contiene un'istruzione di registro: in questo caso, finirai per registrarlo ogni volta.

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

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

Rischio-2) Problema di prestazione: se il contenuto all'interno di orElse richiede molto tempo : il contenuto che richiede molto tempo può essere qualsiasi operazione di I / o chiamata DB, chiamata API, lettura file. Se inseriamo tali contenuti in orElse (), il sistema finirà per eseguire un codice inutile.

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

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

Rischio-3) Problema di stato o bug illegale: se il contenuto all'interno di orElse sta mutando lo stato di un oggetto: potremmo usare lo stesso oggetto in un altro posto, diciamo all'interno della funzione Optional.map e ci può mettere in un bug critico.

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

Quindi, quando possiamo andare con orElse ()? Preferisci usare orElse quando il valore predefinito è un oggetto costante, enum. In tutti i casi precedenti possiamo andare con Optional.orElseGet () (che viene eseguito solo quando Optional contiene un valore non vuoto) invece di Optional.orElse (). Perché?? In orElse, passiamo il valore del risultato predefinito, ma in orElseGet passiamo il fornitore e il metodo del fornitore viene eseguito solo se il valore in Opzionale è nullo.

Key takeaway da questo:

  1. Non utilizzare "Optional.orElse" se contiene un'istruzione di registro.
  2. Non utilizzare "Optional.orElse" se contiene una logica che richiede molto tempo.
  3. Non utilizzare "Optional.orElse" se sta mutando uno stato dell'oggetto.
  4. Usa "Optional.orElse" se dobbiamo restituire una costante, enum.
  5. Preferisci "Optional.orElseGet" nelle situazioni menzionate nei punti 1,2 e 3.

Ho spiegato questo nel punto 2 ( "Optional.map/Optional.orElse"! = "If / else" ) il mio blog medio. Utilizzare Java8 come programmatore e non come programmatore


0

Considerando il seguente codice:

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

se otteniamo valuein questo modo: Optional.<String>ofNullable(null), non v'è alcuna differenza tra orElseGet () e orelse (), ma se otteniamo valuein questo modo: Optional.<String>ofNullable("test"), orelesMethod()in orElseGet()non sarà chiamato ma in orElse()sarà chiamato esso

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.