
Oi, habrozhiteli! A vantagem dos aplicativos modernos está em soluções avançadas, incluindo microsserviços, arquiteturas reativas e streaming de dados. Expressões lambda, fluxos de dados e o tão esperado sistema de módulos da plataforma Java simplificam bastante sua implementação.
O livro ajudará você a aprender novos recursos de complementos modernos, como a API do Streams e o sistema de módulos da plataforma Java. Descubra novas abordagens de competitividade e aprenda como os conceitos de funcionalidade aprimoram o trabalho com o código.
Neste livro: • Novos recursos Java • Streaming de dados e programação reativa • Sistema de módulos da plataforma Java.
Trecho. Capítulo 11. Classe Opcional como a Melhor Alternativa para null
Levante a mão se, durante sua carreira como desenvolvedor Java, você já recebeu uma NullPointerException. Deixe-o levantado se essa exceção for a mais comum que você encontrou. Infelizmente, não o vemos agora, mas é muito provável que sua mão esteja levantada. Também suspeitamos que você esteja pensando algo como: “Sim, eu concordo. Exceções NullPointerException é uma dor de cabeça para qualquer desenvolvedor Java, seja iniciante ou especialista. Mas nada pode ser feito com eles de qualquer maneira, este é o preço que pagamos pelo uso de um design tão conveniente e provavelmente inevitável como links vazios. ” Esta é uma opinião comum no mundo da programação (imperativa); no entanto, talvez essa não seja toda a verdade, mas um preconceito profundamente enraizado.
O especialista britânico em ciência da computação Tony Hoare, que criou links nulos em 1965, enquanto desenvolvia a linguagem ALGOL W, uma das primeiras linguagens de programação digitadas com registros para os quais a memória foi alocada na pilha, mais tarde admitiu que ele fez isso " apenas por causa da facilidade de implementação. ” Embora ele quisesse garantir "total segurança ao usar uma exceção para links vazios, ele achava que essa era a maneira mais conveniente de simular a ausência de um valor. Muitos anos depois, ele se arrependeu dessa decisão, chamando-a de "meu erro de bilhão de dólares". Todos vimos os resultados dessa decisão. Por exemplo, podemos verificar o campo de um objeto para determinar se ele representa um dos dois valores possíveis, apenas para descobrir que não estamos verificando um objeto, mas um ponteiro nulo, e imediatamente obtemos essa NullPointerException irritante.
De fato, Hoar pode ter subestimado o simples custo de corrigir milhões de desenvolvedores de erros causados por links vazios nos últimos 50 anos. De fato, a grande maioria das linguagens de programação1 criadas nas últimas décadas, incluindo Java, são baseadas na mesma decisão de design, talvez por razões de compatibilidade com linguagens mais antigas ou (mais provavelmente), como Hoar disse, “simplesmente por causa da facilidade de implementação " Começamos demonstrando um exemplo simples dos problemas encontrados ao usar null.
11.1 Como simular uma falta de valor
Imagine que você tenha a estrutura de objetos aninhados na Listagem 11.1 para o proprietário de um carro que comprou um seguro de carro.
Listagem 11.1. Modelo de Dados Pessoa / Carro / Seguro
public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } }
O que você acha que é o problema com o código a seguir?
public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); }
Esse código parece bastante razoável, mas muitas pessoas não têm carros, então qual é o resultado de chamar o método getCar nesse caso? Freqüentemente (e em vão) eles retornam um link vazio para indicar a ausência de um valor (nesse caso, para indicar a ausência de uma máquina). Como resultado, chamar o método getInsurance retornará um seguro de link em branco, o que fará com que uma NullPointerException seja lançada em tempo de execução e o programa pare. Mas isso não é tudo. Mas e se o objeto da pessoa fosse nulo? E se getInsurance também retornasse nulo?
11.1.1 Reduzir NullPointerException com verificação de segurança
Como evitar NullPointerException inesperado? Geralmente, você pode adicionar verificações nulas sempre que precisar (e às vezes excedendo os requisitos de programação segura e onde não precisar), e geralmente em estilos diferentes. Nossa primeira tentativa de escrever um método para impedir a geração de uma NullPointerException é mostrada na Listagem 11.2.
Esse método verifica se há nulo sempre que uma variável é desreferenciada, retornando o valor da cadeia "Desconhecido" se pelo menos uma das variáveis encontradas nessa cadeia de desreferenciamento for um valor vazio. A única exceção a essa regra é que não verificamos o nome da companhia de seguros como nulo, porque sabemos que (como qualquer outra empresa) ela deve ter um nome. Observe que conseguimos evitar essa última verificação apenas devido ao conhecimento da área de assunto, mas esse fato não se reflete nas classes Java que modelam nossos dados.
Descrevemos o método na Listagem 11.2 como "profundas dúvidas", porque um padrão de repetição é perceptível nele: toda vez que houver dúvida, a variável não é nula, é necessário adicionar outro bloco aninhado se, aumentando assim o nível de indentação de código. Obviamente, essa técnica não escala bem e reduz a legibilidade, por isso é melhor tentar uma solução diferente. Para evitar esse problema, vamos seguir um caminho diferente, como mostra a Listagem 11.3.
Na segunda tentativa, tentamos evitar o aninhamento profundo de blocos if usando uma estratégia diferente: sempre que encontramos uma variável nula, retornamos o valor da string "Unknown". Mas essa solução também está longe de ser ideal; Agora, o método possui quatro pontos de saída diferentes, o que complica bastante sua manutenção. Além disso, o valor padrão retornado no caso de null - a string "Unknown" - é repetida em três lugares e (esperamos) não contém erros de ortografia! Para evitar isso, é claro, você pode mover a sequência repetida para uma constante.
Além disso, esse processo é propenso a erros. E se você esquecer de verificar se uma das propriedades é nula? Neste capítulo, argumentamos que o uso de nulo para representar uma falta de valor é uma abordagem fundamentalmente errada. É necessária uma maneira melhor de modelar a ausência e a presença de valor.
11.1.2 Problemas encontrados com null
Para resumir o exposto acima, o uso de links vazios em Java leva aos seguintes problemas teóricos e práticos.
- Serve como fonte de erros. NullPointerException é a exceção mais comum (por uma ampla margem) em Java.
- "Infla" o código. A legibilidade piora devido à necessidade de preencher o código com verificações nulas, geralmente profundamente aninhadas.
- Isso não faz sentido. Falta qualquer significado semântico, em particular, tal abordagem para modelar a ausência de significado em uma linguagem com tipificação estática é fundamentalmente errada.
- Viola a ideologia da linguagem Java. Java sempre oculta indicadores dos desenvolvedores, com uma exceção: um ponteiro nulo.
- Cria uma lacuna no sistema de tipos. null não inclui nenhum tipo ou outra informação, portanto, pode ser atribuído a qualquer tipo de link. Isso pode causar problemas ao transferir nulo para outra parte do sistema, onde não há informações sobre o que esse nulo deve ser inicialmente.
Como base para outras soluções viáveis na próxima subseção, examinamos brevemente as possibilidades oferecidas por outras linguagens de programação.
11.1.3 Alternativas ao null em outras linguagens de programação
Nos últimos anos, idiomas como o Groovy conseguiram contornar esse problema, introduzindo um operador de chamada segura (?.), Projetado para trabalhar com segurança com possíveis valores nulos. Para entender como esse processo é implementado na prática, considere o seguinte código Groovy. Ele recupera o nome da companhia de seguros na qual a pessoa especificada segurou o carro:
def carInsuranceName = person?.car?.insurance?.name
Deve ficar claro para você o que esse código faz. Uma pessoa pode não ter um carro, cuja simulação atribuímos nulo ao link do carro do objeto da pessoa. Da mesma forma, uma máquina pode não ter seguro. O operador de chamada segura do Groovy permite que você trabalhe com segurança com links potencialmente vazios sem gerar uma NullPointerException, passando o link vazio pela cadeia de chamadas e retornando nulo se algum valor da cadeia for nulo.
Um recurso semelhante foi proposto para ser implementado no Java 7, mas depois foi decidido não fazer isso. No entanto, por incrível que pareça, o operador de chamada segura não é realmente necessário em Java. O primeiro pensamento de qualquer desenvolvedor Java ao encontrar uma NullPointerException é corrigir rapidamente o problema adicionando uma instrução if para verificar se há nulo antes de chamar seu método. Resolver o problema de maneira semelhante, sem considerar se nulo é aceitável nessa situação específica para o seu algoritmo ou modelo de dados, não leva a uma correção, mas a um erro oculto. E posteriormente para o próximo desenvolvedor (provavelmente você mesmo em uma semana ou um mês), será muito mais difícil encontrar e corrigir esse erro. Na verdade, você apenas varre o lixo debaixo do tapete. O operador de cancelamento de referência nulo e seguro do Groovy é apenas uma vassoura maior e mais poderosa, com a qual você pode fazer esse absurdo sem realmente se preocupar com as conseqüências.
Outras linguagens de programação funcional, como Haskell e Scala, encaram esse problema de maneira diferente. Haskell tem um tipo Maybe, que essencialmente encapsula um valor opcional. Um objeto do tipo Talvez pode conter um valor do tipo especificado ou nada. Haskell não tem o conceito de um link vazio. Em Scala, para encapsular a presença ou ausência de um valor do tipo T, é fornecida uma estrutura lógica semelhante, a opção [T], que discutiremos no capítulo 20. Nesse caso, você deve verificar explicitamente se um valor está presente usando operações do tipo Option, que fornece “verificações nulas” . Agora não é mais possível “esquecer de verificar nulo”, pois o próprio sistema de tipos requer verificação.
Ok, estamos um pouco fora do assunto, e tudo parece bem abstrato. Você provavelmente está se perguntando o que o Java 8. oferece nesse sentido .. Inspirados pela idéia de um valor opcional, os criadores do Java 8 introduziram uma nova classe, java.util.Optional! Neste capítulo, mostramos qual é a vantagem de usá-lo para modelar valores potencialmente ausentes, em vez de atribuir a eles uma referência vazia. Também explicaremos por que essa transição de nulo para opcional exige que o programador revise a ideologia de trabalhar com valores opcionais no modelo de domínio. Por fim, exploraremos as possibilidades dessa nova classe opcional e forneceremos alguns exemplos práticos de seu uso efetivo. Como resultado, você aprenderá como projetar APIs aprimoradas, nas quais o usuário já entende pela assinatura do método se um valor opcional é possível aqui.
11.2 Apresentando a classe opcional
No Java 8, sob a influência das linguagens Haskell e Scala, uma nova classe java.util.Optional parece encapsular um valor opcional. Por exemplo, se você sabe que uma pessoa pode ou não possuir um carro, você não deve declarar a variável carro na classe Person com o tipo Car e atribuir uma referência vazia se a pessoa não tiver um carro; em vez disso, seu tipo deve ser opcional, como mostrado na fig. 11.1

Dado um valor, a classe Opcional serve como um adaptador para ele. Por outro lado, a ausência de um valor é modelada usando o opcional vazio retornado pelo método Optional.empty. Esse método estático de fábrica retorna uma instância única especial da classe Opcional. Você pode estar se perguntando qual é a diferença entre um link vazio e Optional.empty (). Semanticamente, eles podem ser considerados iguais, mas na prática existem enormes diferenças entre eles. Uma tentativa de desreferenciar nulo inevitavelmente leva a um NullPointerException, e Optional.empty () é um objeto válido e viável do tipo Opcional, que pode ser acessado convenientemente. Em breve você verá como exatamente.
Uma diferença semântica prática importante no uso de objetos Opcionais em vez de nulo: declarar uma variável do tipo Opcional diz claramente que um valor vazio é permitido neste momento. E vice-versa, sempre usando o tipo Car e, possivelmente, às vezes atribuindo referências vazias a variáveis desse tipo, você quer dizer que depende apenas do seu conhecimento de domínio para entender se nulo pertence ao domínio de definição de uma determinada variável.
Com isso em mente, você pode refazer o modelo original da Listagem 11.1. Usamos a classe Opcional, como mostra a Listagem 11.4.
Observe que o uso da classe Opcional enriquece a semântica do modelo. Um campo Opcional na classe Pessoa e um campo Opcional na classe Carro refletem o fato de que uma pessoa pode ou não ter um carro, assim como uma máquina pode ou não estar segurada.
Ao mesmo tempo, declarar o nome da companhia de seguros como String, em vez de Opcional, indica claramente que a companhia de seguros deve ter um nome. Portanto, você tem certeza de que receberá uma NullPointerException ao cancelar a referência do nome da companhia de seguros; adicionar verificação nula não é necessário, porque apenas ocultaria o problema. A companhia de seguros deve ter um nome; portanto, se você se deparar com uma empresa sem nome, precisará descobrir o que há de errado com os dados e não adicionar um pedaço de código para ocultar esse fato.

O uso consistente de valores opcionais cria uma distinção clara entre um valor que pode estar ausente e um valor que está ausente devido a um erro no algoritmo ou a um problema nos dados. É importante observar que a classe Opcional não se destina a substituir todos os links vazios por um único. Sua tarefa é ajudar a projetar APIs mais compreensíveis, para que, com a assinatura do método, seja possível entender se um valor opcional pode ser encontrado lá. O sistema do tipo Java força uma opção de descompactação para lidar com a ausência de um valor.
Sobre autores
Raul-Gabriel Urma é o CEO e co-fundador da Cambridge Spark (Reino Unido), uma comunidade educacional líder para pesquisadores e desenvolvedores de dados. Raul foi eleito um dos participantes do programa Java Champions em 2017. Ele trabalhou para Google, eBay, Oracle e Goldman Sachs. Ele defendeu sua tese em engenharia da computação na Universidade de Cambridge. Além disso, ele possui mestrado em engenharia pelo Imperial College London, se formou com distinção e recebeu vários prêmios por propostas de racionalização. Raul apresentou mais de 100 relatórios técnicos em conferências internacionais.
Mario Fusco é um engenheiro de software sênior da Red Hat, envolvido no desenvolvimento do kernel Drools, o mecanismo de regras do JBoss. Ele tem uma vasta experiência no desenvolvimento de Java, participou (muitas vezes como desenvolvedor líder) em muitos projetos corporativos em vários setores, da mídia ao setor financeiro. Entre seus interesses estão programação funcional e linguagens específicas de domínio. Com base nesses dois hobbies, ele criou a biblioteca lambdaj de código aberto, desejando desenvolver uma Java DSL interna para trabalhar com coleções e possibilitar o uso de alguns elementos de programação funcional em Java.
Alan Mycroft é professor do Departamento de Ciência da Computação da Universidade de Cambridge, onde leciona desde 1984. Ele também é funcionário do Robinson College, um dos fundadores da Associação Europeia de Linguagens e Sistemas de Programação e um dos fundadores e administradores da Raspberry Pi Foundation. Ele é formado em matemática (Cambridge) e ciência da computação (Edimburgo). Alan é autor de mais de 100 artigos científicos. Ele foi o supervisor de mais de 20 candidatos a doutorado. Sua pesquisa lida principalmente com o campo das linguagens de programação e sua semântica, otimização e implementação. Por um tempo, ele trabalhou na AT&T Laboratories e no departamento de pesquisa da Intel, e também co-fundou a Codemist Ltd., que lançou o compilador da linguagem C para a arquitetura ARM, chamado Norcroft.
»Mais informações sobre o livro podem ser encontradas no
site do editor»
Conteúdo»
TrechoCupom de 25% de desconto para vendedores ambulantes -
JavaApós o pagamento da versão impressa do livro, um livro eletrônico é enviado por e-mail.