No Java 8 foram introduzidos os Optionals. Estes são extremamente uteis em vários cenários onde não há certeza de retorno de valores validos.
Uma função que tende a causar um pouco de confusão é a .orElse
e .orElseGet
. A princípio parecem iguais mas há diferenças substaciais entre elas no que diz respeito a funcionalidade e performance.
Ao observar a assinatura dos métodos
public T orElse(T other)
public T orElseGet(Supplier<? extends T> other)
Podemos ver claramente que .orElse()
retorna um valor do tipo T
e aceita um valor do mesmo tipo, enquanto .orElseGet()
retorna um valor do tipo T
e aceita uma função como parametro.
Ao checar os javadocs temos:
T orElse(T other)
: retorna o valor se estiver presente, caso contrario retorna otherT orElseGet(Supplier<?> other)
: retorna o valor se estiver, caso contrário invoca other e retorna o valor do seu resultado.
Antes de analisarmos as diferenças, considere o seguinte exemplo:
private static Optional<String> getAnswer(Boolean know) {
if(!know){
System.out.println("call: I don't know");
System.out.println("call: Returning Empty");
return Optional.empty();
} else {
System.out.println("call: I know!");
System.out.println("call: Returning answer!");
return Optional.of("call: 42");
}
}
private static String fallback() {
System.out.println("fallback: Falling back to a random typed number...");
return "fallback: 1234567890";
}
Vamos as diferenças entre eles:
orElse()
Considere o seguinte:
System.out.println(getAnswer(false).orElse(fallback()));
Agora observemos o resultado no console:
call: I don't know
call: Returning Empty
fallback: Falling back to a random typed number...
fallback: 1234567890
Ótimo certo? exatamente o que esperavamos.
Agora, considere que o parametro know = true
:
System.out.println(getAnswer(true).orElse(fallback()));
Observando o console temos:
call: I know!
call: Returning answer!
fallback: Falling back to a random typed number...
call: 42
Notou a diferença? o metodo .orElse
executou tanto o método getAnswer()
como fallback()
e por fim utilizou o primeiro resultado valido que encontrou, neste caso, o retorno do getAnswer()
.
orElseGet()
Assim como no caso do orElse
, considere os exeplos abaixo:
System.out.println(getAnswer(false).orElseGet(() -> fallback()));
Resultados:
call: I don't know
call: Returning Empty
fallback: Falling back to a random typed number...
fallback: 1234567890
Ótimo, exatamente o esperado, assim como no caso do orElse. Agora vejamos o que acontece quando o getAnswer()
retorna um valor válido:
System.out.println(getAnswer(true).orElseGet(() -> fallback()));
Resultados:
call: I know!
call: Returning answer!
call: 42
Note que assim que o getAnswer()
retorna um resultado válido, a execução do próximo metodo (fallback()
) é automaticamente interrompida, retornando o valor do valido do primeiro metodo.
Mas e daí?
Imagine que a função fallback()
modifique arquivos, banco de dados, ou faça chamadas a APIs. Usando orElse()
você pode acidentalmente criar chamadas desnecessárias, gerar dados incorretos, dentre outros problemas.
Em outras situações pode-se causar confusão ao gerar entradas incorretas em logs, como por exemplo:
priceList = getPrices().orElse(getPricesFromYesterday());
Onde cada método (getPrices()
e getPricesFromYesterday()
postem logs indicando que estão sendo usados)
O log teria entradas de ambos metodos:
[DEBUG] Using prices updated today.
[DEBUG] Using prices updated yesteday.
Ao ver um log assim, qual instância de lista de preços está realmente sendo utilizada? É praticamente impossível dizer sem abrir o código e começar a debugar.
Então, por padrão evite usar orElse() para métodos, e use orElseGet() sempre que possível, a não ser que haja um motivo claro e concreto.