Diferencia entre `Opcional.oElse ()` y `Opcional.oElseGet ()`

206

Estoy tratando de entender la diferencia entre los métodos Optional<T>.orElse()y Optional<T>.orElseGet().

La descripción del orElse()método es "Devuelve el valor si está presente, de lo contrario devuelve otro".

Mientras que la descripción del orElseGet()método es "Devuelve el valor si está presente, de lo contrario invoca a otro y devuelve el resultado de esa invocación".

El orElseGet()método toma una interfaz funcional de proveedor, que esencialmente no toma ningún parámetro y devuelve T.

¿En qué situación necesitarías usar orElseGet()? Si tienes un método, T myDefault()¿por qué no lo harías en optional.orElse(myDefault())lugar de hacerlo optional.orElseGet(() -> myDefault())?

No parece que orElseGet()posponga la ejecución de la expresión lambda en algún momento posterior o algo así, entonces, ¿qué sentido tiene? (Pensé que sería más útil si devolviera un dispositivo más seguro Optional<T>que get()nunca arroja un NoSuchElementExceptiony isPresent()siempre devuelve verdadero ... pero evidentemente no lo es, simplemente regresa Tcomo orElse()).

¿Hay alguna otra diferencia que me estoy perdiendo?

jbx
fuente
77
El motivo es que cuando lo utiliza orElseGet, llama al proveedor solo si no hay valor.
Alex Salauyou
9
Ah ok lo tengo. Así, en el caso de orElse()la myDefault()método todavía se llama, pero su valor de retorno no se utiliza.
jbx
3
Pregunta votada porque por lo que he visto malentendidos u olvido de usar orElseGet()puede dar lugar a algunos errores graves: medium.com/alphadev-thoughts/…
softarn
Una buena explicación se encuentra aquí: baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

Respuestas:

172

Tome estos dos escenarios:

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

Si optno contiene un valor, los dos son equivalentes. Pero si opt no contener un valor, ¿cuántos Fooobjetos serán creados?

Ps: por supuesto, en este ejemplo, la diferencia probablemente no sería medible, pero si tiene que obtener su valor predeterminado de un servicio web remoto, por ejemplo, o de una base de datos, de repente se vuelve muy importante.

biziclop
fuente
22
Gracias por la aclaración chicos. Entonces la diferencia es sutil pero significativa. En el segundo caso, no va a crear un nuevo Fooobjeto, mientras que en el primer caso se creará, pero no lo use si hay un valor dentro de la Optional.
jbx
55
@jbx Sí, y en mi ejemplo indirecto, posiblemente no haga ninguna diferencia real, pero si tiene que obtener su valor predeterminado de un servicio web remoto, por ejemplo, o de una base de datos, la diferencia de repente se vuelve muy importante.
biziclop
2
@jbx: estás mezclando dos cosas. Ya hay preguntas sobre SO con respecto a resultados extraños de referencia que simplemente fueron causados ​​por no usar el resultado de un cálculo. La JVM puede hacer eso. Por otro lado, noSystem.out.println() es un cálculo sino una declaración que produce un efecto secundario observable. Y ya dije que los efectos secundarios observables dificultarán las optimizaciones (el flujo de salida de la consola es un recurso externo).
Holger
77
Esa es la primera vez que veo una pregunta en lugar de una respuesta aceptada.
Kirill G.
44
" si tiene que obtener su valor predeterminado de un servicio web remoto, por ejemplo ", este era exactamente mi escenario. En mi caso, el opcional era una consulta, y el valor predeterminado en ausencia de una consulta era obtener todos los valores ... sí, o ElseGet redujo el tiempo de ejecución de esa operación en 1000 veces.
scottysseus
109

Respuesta corta:

  • orElse () siempre llamará a la función dada, lo desee o no, independientemente del Optional.isPresent()valor
  • orElseGet () solo llamará a la función dada cuando elOptional.isPresent() == false

En el código real, es posible que desee considerar el segundo enfoque cuando el recurso requerido es costoso de obtener .

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

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

Para más detalles, considere el siguiente ejemplo con esta función:

public Optional<String> findMyPhone(int phoneId)

La diferencia es la siguiente:

                           X : buyNewExpensivePhone() called

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

Cuando optional.isPresent() == false, no hay diferencia entre dos formas. Sin embargo, cuando optional.isPresent() == true, orElse()siempre llama a la función posterior, lo desee o no.

Finalmente, el caso de prueba utilizado es el siguiente:

Resultado:

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

Código:

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
fuente
Creo que puede tener un error en su imagen, debería decir "orElseGet" a la derecha? Además de eso, gran ejemplo.
Yalla T.
Sí, estás en lo correcto. Gracias :) Lo actualizaré en las próximas horas
nxhoaf
Para el segundo punto, parece que debería ser Optional.isPresent() == falseen su lugar (falso, no cierto)
Manuel Jordan
Gran ejemplo, pero realmente no entiendo cómo los Javadocs para Optional.orElsequé estados If a value is present, returns the value, otherwise returns otherpueden implicar este comportamiento ...
Erik Finnman
Basado en su explicación, me parece que orElse()se comporta similar a la finallyde try-catchexpresión. ¿Estoy en lo correcto?
Mike B.
63

Llegué aquí por el problema que Kudo mencionó.

Estoy compartiendo mi experiencia por los demás.

orElseo orElseGetesa es la pregunta:

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

huellas dactilares

B()...
A
A

orElseevalúa el valor de B () de manera interdependiente del valor de lo opcional. Por lo tanto, orElseGetes vago.

Jin Kwon
fuente
77
No es un problema'. Es el simple hecho de que el argumento de un método se evalúa antes de la ejecución del método. Si pasa B()a un método llamado orElse()o abc()no hace ninguna diferencia, B()se evalúa.
jbx
11
El problema aquí es realmente el nombramiento de los métodos. El orprefijo engaña a los desarrolladores (incluyéndome a mí mismo cuando le pregunté el problema) para pensar que es una operación de cortocircuito, porque eso es a lo que estamos acostumbrados en condiciones booleanas. Sin embargo, no lo es, es solo un nombre de método que tiene oren su prefijo, por lo que sus argumentos serán evaluados, independientemente de si Optionallleva un valor o no. Es lamentable que la denominación sea confusa, no que podamos hacer nada al respecto.
jbx
37

Yo diría que la mayor diferencia entre orElsey orElseGetviene cuando queremos evaluar algo para obtener el nuevo valor en la elsecondición.

Considere este simple ejemplo:

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

Ahora transformemos el ejemplo anterior para usar Optionaljunto con orElse,

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

Ahora transformemos el ejemplo anterior para usar Optionaljunto con orElseGet,

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

Cuando orElsese invoca, apicall().valuese evalúa y se pasa al método. Mientras que, en el caso de orElseGetla evaluación solo ocurre si el oldValueestá vacío. orElseGetpermite una evaluación perezosa.

devang
fuente
44
Perdí muchas veces debido a este comportamiento "extraño" de ifElse (). Diría que tiene sentido preferir ifElseGet () sobre ifElse ()
Enrico Giurin
3

El siguiente ejemplo debería demostrar la diferencia:

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 respuesta también aparece en los documentos.

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

Devuelva el valor si está presente, de lo contrario invoque a otro y devuelva el resultado de esa invocación.

El Supplier no va a ser invocada si los Optionalpresentes. mientras,

public T orElse(T other):

Devuelve el valor si está presente, de lo contrario devuelve otro.

Si otheres un método que devuelve una cadena, se invocará, pero su valor no se devolverá en caso de que Optionalexista.

Maroun
fuente
3

La diferencia es bastante sutil y si no prestas mucha atención, la seguirás usando de manera incorrecta.

La mejor manera de entender la diferencia entre orElse()y orElseGet()es que orElse()siempre se ejecutará si Optional<T>es nulo o no , pero orElseGet()solo se ejecutará cuando Optional<T>sea nulo .

El significado del diccionario de orElse es : - ejecuta la parte cuando algo no está presente, pero aquí contradice, mira el siguiente ejemplo:

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

Salida: nonEmptyOptional no es NULL, todavía estoy siendo ejecutado


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

Salida : emptyOptional es NULL, estoy en ejecución, es normal según el diccionario

Para orElseGet(), El método va según el significado del diccionario, La orElseGet()parte se ejecutará solo cuando el Opcional sea nulo .

Puntos de referencia :

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

Observaciones : orElseGet()claramente ha superado orElse()a nuestro ejemplo particular.

Espero que aclare las dudas de personas como yo que quieren el ejemplo básico básico :)

Vishwa Ratna
fuente
2

Antes que nada, verifique la declaración de ambos métodos.

1) OrElse: ejecuta la lógica y pasa el resultado como argumento.

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

2) OrElseGet: ejecuta la lógica si el valor dentro del opcional es nulo

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

Alguna explicación sobre la declaración anterior: El argumento de "Opcional.o Else" siempre se ejecuta independientemente del valor del objeto en opcional (nulo, vacío o con valor). Siempre tenga en cuenta el punto mencionado anteriormente al usar "Opcional.o Else", de lo contrario, el uso de "Opcional.o Else" puede ser muy arriesgado en la siguiente situación.

Riesgo-1) Problema de registro: si el contenido dentro o Else contiene alguna declaración de registro: en este caso, terminará registrándolo cada vez.

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

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

Riesgo-2) Problema de rendimiento: si el contenido interno o Else requiere mucho tiempo: el contenido intensivo de tiempo puede ser cualquier llamada de DB de operaciones de E / S, llamada de API, lectura de archivos. Si ponemos dicho contenido en orElse (), el sistema terminará ejecutando un código inútil.

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

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

Riesgo-3) Estado ilegal o problema de error: si el contenido dentro o Else está mutando algún estado de objeto: podríamos estar usando el mismo objeto en otro lugar, digamos dentro de la función Opcional.map y nos puede poner en un error crítico.

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

Entonces, ¿cuándo podemos ir con orElse ()? Prefiere usar orElse cuando el valor predeterminado es algún objeto constante, enum. En todos los casos anteriores, podemos ir con Opcional.orElseGet () (que solo se ejecuta cuando Opcional contiene un valor no vacío) en lugar de Opcional.orElse (). ¿¿Por qué?? En orElse, pasamos el valor de resultado predeterminado, pero en orElseGet pasamos el Proveedor y el método del Proveedor solo se ejecuta si el valor en Opcional es nulo.

Conclusiones clave de esto:

  1. No use "Opcional.o Else" si contiene alguna instrucción de registro.
  2. No use "Opcional.o Else" si contiene una lógica que requiere mucho tiempo.
  3. No use "Opcional.o Else" si está mutando algún estado del objeto.
  4. Utilice "Opcional. O Else" si tenemos que devolver una constante, enum.
  5. Prefiera "Opcional.o ElseGet" en las situaciones mencionadas en los puntos 1,2 y 3.

He explicado esto en el punto 2 ( "Opcional.map/Optional.orElse"! = "If / else" ) mi blog medio. Use Java8 como programador, no como codificador

Piyush N
fuente
0

Teniendo en cuenta el siguiente código:

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 conseguimos valuede esta manera: Optional.<String>ofNullable(null), no hay ninguna diferencia entre orElseGet () y OrElse (), pero si conseguimos valuede esta manera: Optional.<String>ofNullable("test"), orelesMethod()en orElseGet()que no sea igualada, pero en la orElse()que se llamará

ratzip
fuente