Os getters Java 8 devem retornar o tipo opcional?

288

Optional O tipo introduzido no Java 8 é algo novo para muitos desenvolvedores.

Um método getter retornando um Optional<Foo>tipo no lugar do clássico é Foouma boa prática? Suponha que o valor possa ser null.

leonprou
fonte
8
Embora seja provável que isso atraia respostas opinativas, é uma boa pergunta. Estou ansioso por uma resposta com fatos reais sobre o assunto.
Justin
8
A questão é se a nulidade é inevitável. Um componente pode ter uma propriedade que pode ser nula, mas ainda assim, o programador que usa esse componente pode decidir manter estritamente essa propriedade null. Portanto, o programador não deveria ter que lidar com Optionalisso. Ou, em outras palavras, nullrealmente representa a ausência de um valor como o resultado de uma pesquisa (quando Optionalapropriado) ou é nullapenas um membro do conjunto de valores possíveis.
Holger
1
Consulte também a discussão sobre @NotNullanotações: stackoverflow.com/q/4963300/873282
koppor:

Respostas:

515

Obviamente, as pessoas farão o que quiserem. Mas nós tivemos uma clara intenção ao adicionar esse recurso, e foi não ser um propósito geral Talvez digita, tanto quanto muitas pessoas nos teria gostado de fazê-lo. Nossa intenção era fornecer um mecanismo limitado para os tipos de retorno do método de biblioteca, onde era necessário haver uma maneira clara de representar "nenhum resultado", e usá null-lo era extremamente provável de causar erros.

Por exemplo, você provavelmente nunca deve usá-lo para algo que retorne uma matriz de resultados ou uma lista de resultados; em vez disso, retorne uma matriz ou lista vazia. Você quase nunca deve usá-lo como um campo de algo ou parâmetro de método.

Eu acho que usá-lo rotineiramente como um valor de retorno para getters seria definitivamente um uso excessivo.

Não há nada de errado com opcional que deve ser evitado, não é exatamente o que muitas pessoas gostaria que fosse e, consequentemente, estávamos bastante preocupados com o risco de excesso de uso zeloso.

(Anúncio do serviço público: NUNCA ligue, a Optional.getmenos que você possa provar que nunca será nulo; em vez disso, use um dos métodos seguros como orElseou ifPresent. Em retrospecto, deveríamos ter chamado getalgo como getOrElseThrowNoSuchElementExceptionou algo que deixasse bem claro que esse era um método altamente perigoso isso prejudicou todo o objetivo Optionalem primeiro lugar.Lição aprendida (UPDATE: Java 10 possui Optional.orElseThrow(), o que é semanticamente equivalente a get(), mas cujo nome é mais apropriado.))

Brian Goetz
fonte
24
(Em relação a última parte) ... e quando estamos confiantes, que o valor é nunca nullpodemos usar orElseThrow(AssertionError::new), ahem ou orElseThrow(NullPointerException::new)...
Holger
25
O que você teria feito diferente se sua intenção tinha sido a introdução de um general-purpose Talvez ou algum tipo? Existe uma maneira pela qual o Optional não se encaixa na conta de um, ou é apenas que a introdução de Optionals em toda uma nova API tornaria isso não-Java-ish?
David Moles
22
Por "tipo de uso geral", quis dizer incorporá-lo ao sistema de tipos da linguagem, em vez de fornecer uma classe de biblioteca que a aproxime. (Alguns idiomas têm tipos para T? (T ou nulo) e T! (T não nulo)) Opcional é apenas uma classe; não podemos fazer conversões implícitas entre Foo e <Foo> opcional, como poderíamos ter com o suporte ao idioma.
Brian Goetz
11
Eu me pergunto há algum tempo por que a ênfase no mundo Java estava na análise estática opcional e não melhor. Opcional tem algumas vantagens, mas a enorme vantagem que nullpossui é a compatibilidade com versões anteriores; Map::getretorna um valor nulo V, não um Optional<V>, e isso nunca vai mudar. No @Nullableentanto, poderia ter sido facilmente anotado . Agora temos duas maneiras de expressar de falta de valor, além de menos incentivo para realmente obter análise estática acontecendo, o que parece ser uma posição pior para se estar.
yshavit
21
Eu não tinha ideia de que usá-lo como um valor de retorno para uma propriedade não era o que vocês pretendiam até ler esta resposta aqui no StackOverflow. Na verdade, fui levado a acreditar que era exatamente o que vocês pretendiam depois de ler este artigo no site da Oracle: oracle.com/technetwork/articles/java/… Obrigado por esclarecer
Jason Thompson
73

Depois de pesquisar um pouco, encontrei uma série de coisas que podem sugerir quando isso for apropriado. O mais autoritário é a seguinte citação de um artigo da Oracle:

"É importante observar que a intenção da classe Opcional não é substituir todas as referências nulas . Em vez disso, seu objetivo é ajudar a projetar APIs mais compreensíveis, de modo que, apenas lendo a assinatura de um método, você possa dizer se pode esperar um valor opcional. Isso força você a desembrulhar ativamente um opcional para lidar com a ausência de um valor ". - Cansado de exceções de ponteiro nulo? Considere o uso opcional do Java SE 8!

Eu também encontrei este trecho do Java 8 Opcional: Como usá-lo

"Opcional não se destina a ser usado nesses contextos, pois não nos comprará nada:

  • na camada de modelo de domínio (não serializável)
  • em DTOs (mesma razão)
  • nos parâmetros de entrada dos métodos
  • nos parâmetros do construtor "

O que também parece suscitar alguns pontos válidos.

Não consegui encontrar conotações negativas ou sinais vermelhos para sugerir que isso Optionaldeve ser evitado. Penso que a ideia geral é, se for útil ou melhorar a usabilidade da sua API, use-a.

Justin
fonte
11
stackoverflow.com/questions/25693309 - parece Jackson apoia-lo já, portanto, "não serializável" já não passa como uma razão válida :)
Vlasec
1
Eu sugiro ter o seu endereço de resposta por que usar Optionalparâmetros de entrada de métodos (mais especificamente construtores) "não nos compra nada". dolszewski.com/java/java-8-optional-use-cases contém uma boa explicação.
Gili
Descobri que currying resultados opcionais que precisam ser transformados em outras APIs requer um parâmetro opcional. O resultado é uma API bastante compreensível. Veja stackoverflow.com/a/31923105/105870
Karl the Pagan
1
O link está quebrado. Você poderia atualizar a resposta?
softarn
2
O problema é que muitas vezes não melhora a API, apesar das melhores intenções do desenvolvedor. Eu tenho coletado exemplos de maus usos do Opcional , todos retirados do código de produção, que você pode conferir.
MiguelMunoz
20

Eu diria que, em geral, é uma boa ideia usar o tipo opcional para valores de retorno que podem ser anuláveis. No entanto, para estruturas, presumo que a substituição de getters clássicos por tipos opcionais cause muitos problemas ao trabalhar com estruturas (por exemplo, Hibernate) que dependem de convenções de codificação para getters e setters.

Claas Wilke
fonte
14
Esse conselho é exatamente o tipo de coisa que eu quis dizer com "estamos preocupados com o risco de uso excessivo de zelo" em stackoverflow.com/a/26328555/3553087 .
Brian Goetz
13

O motivo Optionalfoi adicionado ao Java é porque este:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

é mais limpo que isso:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Meu argumento é que o Optional foi escrito para suportar a programação funcional , que foi adicionada ao Java ao mesmo tempo. (O exemplo é cortesia de um blog de Brian Goetz . Um exemplo melhor pode usar o orElse()método, pois esse código gera uma exceção de qualquer maneira, mas você entendeu.)

Mas agora, as pessoas estão usando o Opcional por um motivo muito diferente. Eles o estão usando para solucionar uma falha no design da linguagem. A falha é esta: não há como especificar quais parâmetros e valores de retorno de uma API podem ser nulos. Isso pode ser mencionado nos javadocs, mas a maioria dos desenvolvedores nem escreve javadocs para seu código, e muitos não verificam os javadocs enquanto escrevem. Portanto, isso leva a muito código que sempre verifica valores nulos antes de usá-los, mesmo que eles geralmente não possam ser nulos porque já foram validados repetidamente nove ou dez vezes na pilha de chamadas.

Acho que havia uma verdadeira sede de solucionar essa falha, porque muitas pessoas que viram a nova classe Opcional assumiram que seu objetivo era adicionar clareza às APIs. É por isso que as pessoas fazem perguntas como "os getters devem retornar opcionais?" Não, provavelmente não deveriam, a menos que você espere que o getter seja usado na programação funcional, o que é muito improvável. De fato, se você observar onde o Optional é usado na API Java, é principalmente nas classes Stream, que são o núcleo da programação funcional. (Não verifiquei muito bem, mas as classes Stream podem ser o único local em que são usadas.)

Se você planeja usar um getter em um pouco de código funcional, pode ser uma boa idéia ter um getter padrão e um segundo que retorne Opcional.

Ah, e se você precisar que sua classe seja serializável, não use absolutamente Opcional.

Os opcionais são uma solução muito ruim para a falha da API porque: a) são muito detalhados eb) nunca foram destinados a resolver esse problema.

Uma solução muito melhor para a falha da API é o Verificador de Nulidade . Este é um processador de anotação que permite especificar quais parâmetros e valores de retorno podem ser nulos, anotando-os com @Nullable. Dessa maneira, o compilador pode varrer o código e descobrir se um valor que pode ser realmente nulo está sendo passado para um valor em que nulo não é permitido. Por padrão, ele assume que nada pode ser nulo, a menos que seja anotado. Dessa forma, você não precisa se preocupar com valores nulos. Passar um valor nulo para um parâmetro resultará em um erro do compilador. Testar um objeto para null que não pode ser nulo produz um aviso do compilador. O efeito disso é alterar o NullPointerException de um erro de tempo de execução para um erro em tempo de compilação.

Isso muda tudo.

Quanto aos seus colaboradores, não use Opcional. E tente criar suas classes para que nenhum dos membros possa ser nulo. E talvez tente adicionar o Verificador de Nulidade ao seu projeto e declarar seus getters e parâmetros de setter @Nullable, se necessário. Eu só fiz isso com novos projetos. Provavelmente produz muitos avisos em projetos existentes escritos com muitos testes supérfluos para nulo; portanto, pode ser difícil atualizar. Mas também vai pegar muitos bugs. Eu amo isso. Meu código é muito mais limpo e mais confiável por causa disso.

(Também há uma nova linguagem que trata disso. O Kotlin, que é compilado com o código de bytes Java, permite especificar se um objeto pode ser nulo quando você o declara. É uma abordagem mais limpa.)

Adenda à postagem original (versão 2)

Depois de pensar muito, cheguei à conclusão de que é aceitável retornar Opcional com uma condição: que o valor recuperado possa realmente ser nulo. Eu tenho visto muitos códigos em que as pessoas rotineiramente retornam Opcional de getters que não podem retornar nulo. Eu vejo isso como uma prática de codificação muito ruim, que apenas adiciona complexidade ao código, o que aumenta a probabilidade de erros. Mas quando o valor retornado puder ser realmente nulo, vá em frente e envolva-o em um Opcional.

Lembre-se de que os métodos projetados para programação funcional e que exigem uma referência de função serão (e devem) escritos em duas formas, uma das quais usa Opcional. Por exemplo, Optional.map()e Optional.flatMap()ambos aceitam referências de função. O primeiro faz uma referência a um getter comum e o segundo leva um que retorna Opcional. Portanto, você não está fazendo um favor a ninguém retornando um Opcional em que o valor não pode ser nulo.

Dito tudo isso, ainda vejo que a abordagem usada pelo Nullness Checker é a melhor maneira de lidar com nulos, pois eles transformam NullPointerExceptions de bugs de tempo de execução para compilar erros de tempo.

MiguelMunoz
fonte
2
Esta resposta parece mais apropriada para mim. Opcional foi adicionado apenas no java 8 quando os fluxos foram adicionados. E apenas as funções de fluxo retornam opcional, tanto quanto eu vi.
Archit
1
Eu acho que essa é uma das melhores respostas. As pessoas sabem o que é opcional e como ele funciona. Mas a parte mais confusa é / era onde usá-lo e como usá-lo. ao ler isso, tira muitas dúvidas.
ParagFlume 15/01/19
3

Se você estiver usando serializadores modernos e outras estruturas que entendem Optional, descobri que essas diretrizes funcionam bem ao escrever Entitybeans e camadas de domínio:

  1. Se a camada de serialização (geralmente um banco de dados) permitir um nullvalor para uma célula na coluna BARda tabela FOO, o getter Foo.getBar()poderá retornar Optionalindicando ao desenvolvedor que é razoável esperar que esse valor seja nulo e eles devem lidar com isso. Se o banco de dados garantir que o valor não será nulo, o getter não deve agrupar isso em um Optional.
  2. Foo.bardeve ser privatee não ser Optional. Realmente não há razão para ser, Optionalse for private.
  3. O levantador Foo.setBar(String bar)deve usar o tipo de bare não Optional . Se não houver problema em usar um nullargumento, informe-o no comentário do JavaDoc. Se não for bom usar nulluma IllegalArgumentExceptionou alguma lógica comercial apropriada, IMHO é mais apropriado.
  4. Os construtores não precisam de Optionalargumentos (por razões semelhantes ao ponto 3). Geralmente, incluo apenas argumentos no construtor que devem ser não nulos no banco de dados de serialização.

Para tornar o acima mencionado mais eficiente, convém editar seus modelos de IDE para gerar getters e modelos correspondentes para etc. toString(), equals(Obj o)ou usar campos diretamente para esses (a maioria dos geradores de IDE já lida com nulos).

Marca
fonte