Por que usar um preferencial opcional para verificar a variável como nula?


25

Veja os dois exemplos de código:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

Tanto quanto posso dizer, a diferença mais óbvia é que o Opcional requer a criação de um objeto adicional.

No entanto, muitas pessoas começaram a adotar rapidamente os opcionais. Qual é a vantagem de usar opcionais versus uma verificação nula?


4
você quase nunca deseja usar o isPresent, normalmente deve usar o ifPresent ou o map
jk.

6
@jk. Porque as ifdeclarações são muuuuito última década, e todo mundo está usando abstrações de mônada e lambdas agora.
user253751

2
@immibis Certa vez, tive um longo debate sobre esse tópico com outro usuário. O ponto é que, ao if(x.isPresent) fails_on_null(x.get)sair do sistema de tipos, é necessário manter a garantia de que o código não irá "entrar na sua cabeça" pela distância (reconhecidamente curta) entre a condição e a chamada de função. No optional.ifPresent(fails_on_null)sistema de tipos faz essa garantia para você, e você não precisa se preocupar.
Ziggystar

11
A principal falha no Java com o uso Optional.ifPresent(e várias outras construções Java) é que você só pode modificar efetivamente variáveis ​​finais e não pode lançar exceções verificadas. Essa é a razão suficiente para evitar frequentementeifPresent , infelizmente.
Mike

Respostas:


19

Optionalaproveita o sistema de tipos para realizar um trabalho que, de outra forma, você teria que fazer tudo em sua mente: lembrando se uma determinada referência pode ou não ser null. Isso é bom. É sempre inteligente deixar o compilador lidar com drogas entediantes e reservar o pensamento humano para um trabalho criativo e interessante.

Sem Optional, toda referência em seu código é como uma bomba não explodida. Acessá-lo pode fazer algo útil, ou pode encerrar seu programa com uma exceção.

Com Opcional e sem null, todos os acessos a uma referência normal são bem-sucedidos e todas as referências a um Opcional são bem-sucedidos, a menos que esteja desativado e você não tenha verificado isso. Essa é uma grande vitória em manutenção.

Infelizmente, a maioria dos idiomas que agora oferecem Optionalnão foi abolida null; portanto, você só pode lucrar com o conceito instituindo uma política estrita de "absolutamente nullnunca". Portanto, Optionalpor exemplo, Java não é tão atraente quanto deveria ser idealmente.


Desde a sua resposta é a de cima, pode valer a pena adicionar o uso de Lambdas como uma vantagem - para quem lê este no futuro (ver minha resposta)
William Dunne

A maioria das linguagens modernas fornece tipos não anuláveis, Kotlin, Typescript, AFAIK.
Zen

5
Na IMO, isso é melhor tratado com uma anotação @Nullable. Não há razão para usar Optionalapenas para lembrá-lo que o valor pode ser nulo
hilikus

18

Um Optionaltraz uma digitação mais forte em operações que podem falhar, como as outras respostas abordaram, mas isso está longe de ser a coisa mais interessante ou valiosa Optionalstrazida para a mesa. Muito mais útil é a capacidade de atrasar ou evitar a verificação de falhas e de compor facilmente muitas operações que podem falhar.

Considere se você teve sua optionalvariável do código de exemplo e precisou executar duas etapas adicionais que podem falhar potencialmente. Se alguma etapa ao longo do caminho falhar, você deseja retornar um valor padrão. Usando Optionalscorretamente, você acaba com algo assim:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

Com nulleu teria que verificar três vezes nullantes de prosseguir, o que adiciona muita complexidade e dores de cabeça de manutenção ao código. Optionalster essa verificação incorporada às funções flatMape orElse.

Observe que eu não liguei isPresentuma vez, o que você deve considerar um cheiro de código ao usar Optionals. Isso não significa necessariamente que você nunca deve usar isPresent, apenas que você deve examinar fortemente qualquer código que faça isso, para ver se há uma maneira melhor. Caso contrário, você está certo, apenas obtém um benefício de segurança do tipo marginal em relação ao uso null.

Observe também que não estou tão preocupado em encapsular tudo isso em uma única função, a fim de proteger outras partes do meu código de ponteiros nulos de resultados intermediários. Se faz mais sentido ter minha .orElse(defaultValue)outra função, por exemplo, tenho muito menos escrúpulos em colocá-la lá, e é muito mais fácil compor as operações entre diferentes funções, conforme necessário.


10

Ele destaca a possibilidade de nulo como uma resposta válida, que as pessoas geralmente assumem (com ou sem razão) não será retornada. Destacar as poucas vezes em que nulo é válido permite omitir seu código com muitos números de verificações nulas inúteis.

Idealmente, definir uma variável como null seria um erro de tempo de compilação em qualquer lugar, exceto em um opcional; eliminando exceções de ponteiro nulo em tempo de execução. Obviamente, a compatibilidade com versões anteriores impede isso, infelizmente


4

Deixe-me lhe dar um exemplo:

class UserRepository {
     User findById(long id);
}

É bastante claro para que serve esse método. Mas o que acontece se o repositório não contém usuário com o ID fornecido? Poderia lançar uma exceção ou talvez retorne nulo?

Segundo exemplo:

class UserRepository {
     Optional<User> findById(long id);
}

Agora, o que acontece aqui se não houver usuário com o ID fornecido? Certo, você obtém a instância Optional.absent () e pode verificá-la com Optional.isPresent (). A assinatura do método com Opcional é mais explícita sobre seu contrato. É imediatamente claro como o método deve se comportar.

Também tem um benefício ao ler o código do cliente:

User user = userRepository.findById(userId).get();

Treinei meus olhos para ver .get () como um cheiro de código imediato. Isso significa que o cliente intencionalmente ignora o contrato do método invocado. É muito mais fácil ver isso do que sem o Opcional, porque nesse caso você não sabe imediatamente qual é o contrato do método chamado (se ele permite nulo ou não em seu retorno); portanto, é necessário verificar primeiro.

Opcional é usado com a convenção de que métodos com tipo de retorno Opcional nunca retornam nulo e geralmente também com a convenção (menos forte) de que o método sem Tipo de retorno opcional nunca retorna nulo (mas é melhor anotá-lo com @Nonnull, @NotNull ou similar) .


3

Um Optional<T>permite que você tenha "falha" ou "nenhum resultado" como um valor de resposta / retorno válido para seus métodos (pense, por exemplo, em uma pesquisa no banco de dados). Usar um em Optionalvez de usar nullpara indicar falha / nenhum resultado tem algumas vantagens:

  • Ele comunica claramente que "falha" é uma opção. O usuário do seu método não precisa adivinhar se nullpode ser retornado.
  • O valor null, pelo menos na minha opinião, não deve ser usado para indicar nada, pois a semântica pode não ser totalmente clara. Deve ser usado para informar ao coletor de lixo que o objeto referido anteriormente pode ser coletado.
  • A Optionalclasse fornece muitos métodos legais para trabalhar condicionalmente com os valores (por exemplo, ifPresent()) ou definir valores padrão de maneira transparente ( orElse()).

Infelizmente, isso não elimina completamente a necessidade de nullverificações no código, pois o Optionalobjeto em si ainda pode estar null.


4
Não é realmente para isso que é opcional. Para relatar um erro, use uma exceção. Se você não pode fazer isso, use um tipo que contém quer um resultado ou um código de erro .
James Youngman 07/02

Eu discordo fortemente. Optional.empty () é uma ótima maneira de mostrar falha ou inexistência na minha opinião.
LegendLength

@LegendLength, devo discordar totalmente em caso de falha. Se uma função falha, ele seria melhor me dar uma explicação, e não apenas um vazio, "Eu falhei"
Winston Ewert

Desculpe, eu concordo, quero dizer 'falha esperada', pois não encontrei um funcionário com o nome 'x' durante uma pesquisa.
LegendLength

3

Além das outras respostas, a outra grande vantagem dos Opcionais quando se trata de escrever código limpo é que você pode usar uma expressão Lambda no caso de o valor estar presente, conforme mostrado abaixo:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));

2

Considere o seguinte método:

void dance(Person partner)

Agora, vamos ver um código de chamada:

dance(null);

Isso causa uma falha potencialmente difícil de rastrear em outro lugar, porque dancenão esperava um nulo.

dance(optionalPerson)

Isso causa um erro de compilação, porque a dança esperava um Personnão umOptional<Person>

dance(optionalPerson.get())

Se eu não tivesse uma pessoa, isso causaria uma exceção nessa linha, no ponto em que eu tentava convertê-la ilegalmente Optional<Person>em uma Pessoa. A chave é que, ao contrário de passar um nulo, o exceto rastreia até aqui, não em outro local no programa.

Para juntar tudo: o uso incorreto de opcionais produz problemas mais fáceis de localizar, o uso nulo causa problemas difíceis de localizar.


11
Se você estiver lançando exceções ao chamar Optional.get()seu código, ele é mal gravado - você quase nunca deve chamar Optional.get sem verificar Optional.isPresent()primeiro (como indicado no OP) ou pode escrever optionalPersion.ifPresent(myObj::dance).
Chris Cooper

@QmunkE, sim, você só deve chamar Optional.get () se já souber que o opcional não está vazio (porque você chamou isPresent ou porque seu algoritmo o garante). No entanto, eu me preocupo com as pessoas que verificam cegamente isPresent e depois ignoram os valores ausentes, criando uma série de falhas silenciosas que serão difíceis de rastrear.
Winston Ewert

11
Eu gosto de opcionais. Mas direi que ponteiros nulos antigos e simples raramente são um problema no código do mundo real. Eles quase sempre falham perto da linha de código incorreta. E acho que isso é exagerado pelas pessoas quando se fala sobre esse assunto.
LegendLength

@LegendLength, minha experiência é que 95% dos casos são realmente rastreáveis ​​para identificar de onde vem o NULL. Os 5% restantes, no entanto, são extremamente difíceis de rastrear.
Winston Ewert

-1

No Objective-C, todas as referências a objetos são opcionais. Por causa disso, o código como você postou é bobo.

if (variable != nil) {
    [variable doThing];
}

Código como o acima é inédito no Objective-C. Em vez disso, o programador iria apenas:

[variable doThing];

E se variable contiver um valor, doThingserá chamado nele. Caso contrário, não há mal. O programa o tratará como não operacional e continuará sendo executado.

Esse é o benefício real dos opcionais. Você não precisa desarrumar seu código if (obj != nil).


Não é apenas uma forma de falha silenciosa? Em alguns casos, você pode se importar que o valor seja nulo e desejar fazer algo sobre isso.
LegendLength

-3

if (optional.isPresent ()) {

O problema óbvio aqui é que se "opcional" realmente estiver ausente (ou seja, for nulo), seu código explodirá com uma NullReferenceException (ou similar) tentando chamar qualquer método em uma referência de objeto nulo!

Você pode escrever um método estático, da classe Helper, que determine a nulidade de qualquer tipo de objeto, algo como isto:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

ou, talvez, mais útil:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

Mas será que algum deles é realmente mais legível / compreensível / sustentável do que o original?

if ( null == optional ) ... 
if ( null != optional ) ... 
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.