Diferença entre `Optional.orElse ()` e `Optional.orElseGet ()`

206

Estou tentando entender a diferença entre os métodos Optional<T>.orElse()e Optional<T>.orElseGet().

A descrição para o orElse()método é "Retorne o valor se presente, caso contrário, retorne outro".

Embora, a descrição para o orElseGet()método seja "Retorne o valor se presente, caso contrário, invoque other e retorne o resultado dessa invocação".

O orElseGet()método utiliza uma interface funcional de fornecedor, que essencialmente não aceita parâmetros e retornos T.

Em que situação você precisaria usar orElseGet()? Se você tem um método T myDefault()por que você não apenas fazer optional.orElse(myDefault())mais do que optional.orElseGet(() -> myDefault())?

Parece que não orElseGet()está adiando a execução da expressão lambda para um momento posterior ou algo assim, então qual é o sentido disso? (Eu pensaria que seria mais útil se retornasse um mais seguro, Optional<T>que get()nunca lança umNoSuchElementException e isPresent()sempre retorna verdadeiro ... mas, evidentemente, não é, apenas retorna TcomoorElse() ).

Falta alguma outra diferença?

jbx
fonte
7
O motivo é que, quando você o usa orElseGet, chama o fornecedor apenas se o valor estiver ausente.
Alex Salauyou
9
Ah, entendi. Assim, no caso orElse()do myDefault()método ainda é chamado, mas seu valor de retorno só não é usado.
JBX
3
Upvoted pergunta porque pelo que eu vi mal-entendido ou simplesmente esquecer de uso orElseGet()pode resultar em alguns erros graves: medium.com/alphadev-thoughts/...
softarn
Uma boa explicação é encontrada aqui: baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

Respostas:

172

Tome estes dois cenários:

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

Se optnão contém um valor, os dois são realmente equivalentes. Mas se opt não conter um valor, quantasFoo objetos serão criados?

Ps: é claro que neste exemplo a diferença provavelmente não seria mensurável, mas se você precisar obter seu valor padrão de um serviço da Web remoto, por exemplo, ou de um banco de dados, de repente, isso se tornará muito importante.

Biziclop
fonte
22
Obrigado pelo esclarecimento pessoal. Portanto, a diferença é sutil, mas significativa. No segundo caso, ele não criará um novo Fooobjeto, enquanto no primeiro caso o criará, mas não o utilizará se houver um valor dentro do Optional.
JBX
5
@jbx Sim, e no meu exemplo noddy, sem dúvida, não faz nenhuma diferença real, mas se você precisar obter seu valor padrão de um serviço Web remoto, por exemplo, ou de um banco de dados, a diferença repentinamente se tornará muito importante.
biziclop
2
@ jbx: você está misturando duas coisas. Já existem perguntas sobre SO relacionadas a resultados de benchmark estranhos que simplesmente foram causados ​​por não usar o resultado de um cálculo. A JVM pode fazer isso. Por outro lado, nãoSystem.out.println() é um cálculo, mas uma declaração que produz um efeito colateral observável. E eu já disse que efeitos colaterais observáveis ​​atrapalharão as otimizações (o fluxo de saída do console é um recurso externo).
Holger
7
É a primeira vez que vejo uma pergunta em vez de uma resposta ser aceita.
precisa saber é o seguinte
4
" se você tiver que obter seu valor padrão de um serviço da Web remoto, por exemplo ", esse foi exatamente o meu cenário. No meu caso, o opcional era uma consulta, e o padrão, na ausência de uma consulta, era buscar todos os valores ... sim, ouElseGet reduziu o tempo de execução dessa operação em 1000 vezes.
scottysseus
109

Resposta curta:

  • orElse () sempre chamará a função fornecida, quer você queira ou não, independentemente do Optional.isPresent()valor
  • orElseGet () chamará apenas a função fornecida quando oOptional.isPresent() == false

No código real, convém considerar a segunda abordagem quando o recurso necessário for caro .

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

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

Para mais detalhes, considere o seguinte exemplo com esta função:

public Optional<String> findMyPhone(int phoneId)

A diferença é a seguinte:

                           X : buyNewExpensivePhone() called

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

Quando optional.isPresent() == false, não há diferença entre duas maneiras. No entanto, quando optional.isPresent() == true, orElse()sempre chama a função subseqüente, quer você queira ou não.

Por fim, o caso de teste usado é o seguinte:

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
fonte
Eu acho que você pode ter um erro na sua foto, ele deve dizer "orElseGet" à direita? Além disso, ótimo exemplo.
Yalla T.
Sim você está correto. Obrigado :) Eu vou atualizá-lo em próximas horas
nxhoaf
Para o segundo ponto, parece que deveria ser Optional.isPresent() == false(falso, não é verdadeiro) #
58131 Manuel Jordan
Grande exemplo - mas eu realmente não entendo como o Javadocs para Optional.orElseque os estados If a value is present, returns the value, otherwise returns otherpodem implicar este comportamento ...
Erik Finnman
Com base na sua explicação, pois me parece que orElse()se comporta semelhante a finallyde try-catchexpressão. Estou correcto?
Mike B.
63

Cheguei aqui para o problema Kudo mencionou.

Estou compartilhando minha experiência para os outros.

orElse, ou orElseGet, eis a questão:

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

impressões

B()...
A
A

orElseavalia o valor de B () interdependentemente do valor do opcional. Assim, orElseGeté preguiçoso.

Jin Kwon
fonte
7
Não é um problema'. É o simples fato de o argumento para um método ser avaliado antes da execução do método. Se você passar B()para um método chamado orElse()ou abc()não fizer nenhuma diferença, B()será avaliado.
JBX
11
A questão aqui é realmente a nomeação dos métodos. O orprefixo leva os desenvolvedores (inclusive eu quando perguntei o problema) a pensar que é uma operação em curto-circuito, porque é a isso que estamos acostumados em condições booleanas. No entanto, não é, é apenas um nome de método que possui orseu prefixo; portanto, seus argumentos serão avaliados, independentemente de o Optionalvalor estar ou não. É lamentável que o nome seja confuso, não que possamos fazer algo a respeito.
JBX
37

Eu diria que a maior diferença entre orElsee orElseGetvem quando queremos avaliar algo para obter o novo valor na elsecondição.

Considere este exemplo simples -

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

Agora vamos transformar o exemplo acima para usar Optionaljunto com orElse,

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

Agora vamos transformar o exemplo acima para usar Optionaljunto com orElseGet,

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

Quando orElseé chamado, o apicall().valueé avaliado e passado para o método. Enquanto que, no caso da orElseGetavaliação, isso só acontece se o oldValueestiver vazio. orElseGetpermite uma avaliação preguiçosa.

devang
fonte
4
Eu desperdicei muitas vezes por causa desse comportamento "estranho" de ifElse (). Eu diria que não faz sentido preferir ifElseGet () sobre IfElse ()
Enrico Giurin
3

O exemplo a seguir deve demonstrar a diferença:

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

A resposta também aparece nos documentos.

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

Retorne o valor se presente, caso contrário, invoque other e retorne o resultado dessa invocação.

O Supplier não será invocado se os Optionalpresentes. enquanto que,

public T orElse(T other):

Retorne o valor, se presente, caso contrário, retorne outro.

Se otheré um método que retorna uma string, ela será invocada, mas seu valor não será retornado caso Optionalexista.

Maroun
fonte
3

A diferença é bastante sutil e se você não prestar muita atenção, continuará usando-o de maneira errada.

A melhor maneira de entender a diferença entre orElse()e orElseGet()é que orElse()sempre será executada se Optional<T>for nula ou não , mas orElseGet()somente será executada quando Optional<T>for nula .

O significado do dicionário de orElse é : - executar a parte quando algo não estiver presente, mas aqui contradiz, veja o exemplo abaixo:

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

Saída: nonEmptyOptional não é NULL, ainda estou sendo executado


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

Saída : emptyOptional é NULL, estou sendo executado, é normal conforme o dicionário

Para orElseGet(), O método segue o significado do dicionário. A orElseGet()parte será executada apenas quando o Opcional for nulo .

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

Observações : orElseGet()superou claramente o orElse()nosso exemplo em particular.

Espero que esclareça as dúvidas de pessoas como eu que desejam o exemplo básico do terreno :)

Vishwa Ratna
fonte
2

Antes de tudo, verifique a declaração de ambos os métodos.

1) OrElse: Execute a lógica e passe o resultado como argumento.

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

2) OrElseGet: executar lógica se o valor dentro do opcional for nulo

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

Algumas explicações sobre a declaração acima: O argumento “Optional.orElse” sempre é executado independentemente do valor do objeto em opcional (nulo, vazio ou com valor). Sempre considere o ponto acima mencionado ao usar “Optional.orElse”, caso contrário, o uso de “Optional.orElse” pode ser muito arriscado na seguinte situação.

Risco-1) Problema no log: se o conteúdo dentro do orElse contiver alguma instrução de log: Nesse caso, você terminará o log todas as vezes.

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

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

Risco-2) Problema de desempenho: se o conteúdo no orElse é demorado: o conteúdo intensivo pode ser qualquer chamada de banco de dados de operações de E / S, chamada de API, leitura de arquivo. Se colocarmos esse conteúdo em orElse (), o sistema acabará executando um código inútil.

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

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

Risco-3) Estado ilegal ou problema de bug: se o conteúdo dentro do orElse está modificando algum estado do objeto: Podemos estar usando o mesmo objeto em outro local, digamos na função Optional.map e isso pode nos colocar em um bug 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;
}

Então, quando podemos ir com orElse ()? Prefira usar orElse quando o valor padrão for algum objeto constante, enum. Em todos os casos acima, podemos usar Optional.orElseGet () (que é executado apenas quando Optional contém valor não vazio) em vez de Optional.orElse (). Por quê?? Em orElse, passamos o valor padrão do resultado, mas em orElseGet passamos como Supplier e o método de Supplier somente é executado se o valor em Opcional for nulo.

Principais conclusões disso:

  1. Não use “Optional.orElse” se ele contiver alguma instrução de log.
  2. Não use “Optional.orElse” se ele contiver lógica demorada.
  3. Não use “Optional.orElse” se estiver modificando algum estado do objeto.
  4. Use “Optional.orElse” se precisarmos retornar uma constante, enumeração.
  5. Prefira “Optional.orElseGet” nas situações mencionadas nos pontos 1,2 e 3.

Eu expliquei isso no ponto 2 ( “Optional.map/Optional.orElse”! = “If / else” ) no meu blog médio. Use Java8 como programador e não como codificador

Piyush N
fonte
0

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

se conseguirmos valuedesta forma: Optional.<String>ofNullable(null), não há diferença entre orElseGet () e OrElse (), mas se conseguirmos valuedesta forma: Optional.<String>ofNullable("test"), orelesMethod()em orElseGet()não será chamado, mas em orElse()que será chamado

ratzip
fonte