Prefácio
Ponteiros nulos são as exceções mais comuns e irritantes que odiamos. Para impedir que as dicas nulas sejam exceções, você não deve escrever muitos julgamentos não nulos em seu código.
O Java 8 apresenta uma nova classe opcional. Para evitar a ocorrência de indicadores nulos, não há necessidade de escrever um grande número de julgamentos if(obj!=null) , desde que você precise carregar os dados opcionais, é um contêiner que envolve o objeto.
Dizem que nenhum programador que encontrou uma exceção de ponteiro nulo não é um programador Java, e Null realmente causou muitos problemas. O Java 8 apresenta uma nova classe chamada java.util.Optional para evitar muitos problemas causados por nulo.
Vamos ver que danos uma referência nula pode causar. Primeiro, crie um computador de classe, a estrutura é mostrada na figura abaixo:
O que acontece quando chamamos o seguinte código?
String versão = computador.getSoundcard (). Getusb (). Getversion ();
O código acima parece estar bem, mas muitos computadores (como o Raspberry Pi) realmente não têm cartões de som, portanto, chamar getSoundcard() definitivamente lançará uma exceção de ponteiro nulo.
Um método regular, mas ruim, é retornar uma referência nula para indicar que o computador não possui uma placa de som, mas isso significa que o método getusb () será chamado em uma referência vazia, o que obviamente lançará uma exceção de controle durante o programa em execução, fazendo com que o programa pare de funcionar. Pense nisso, como é embaraçoso aparecer de repente quando o seu programa está em execução em um computador cliente?
A grande ciência da computação Tony Hoare escreveu uma vez: "Acho que as citações nulas foram criadas em 1965, o que resultou em um bilhão de dólares em perdas. A maior tentação para mim ao usar citações nulas era que era fácil de implementar".
Então, como podemos evitar exceções do ponteiro nulo quando o programa está em execução? Você precisa estar alerta e constantemente verificar se há possíveis indicadores nulos, assim:
String versão = "desconhecido"; if (computador! = null) {Soundcard Soundcard = Computer.getSoundCard (); if (Soundcard! = null) {USB USB = Soundcard.getusb (); if (usb! = null) {versão = usb.getversion (); }}} No entanto, você pode ver que o código acima tem muitas verificações nulas e toda a estrutura do código se torna muito feia. Mas temos que usar esse julgamento para garantir que não haja indicadores nulos quando o sistema estiver em execução. É simplesmente irritante julgar se houver muitas referências vazias em nosso código de negócios, e isso também leva a baixa legibilidade do nosso código.
Se você esquecer de verificar se o valor está vazio, as referências nulas também têm grandes problemas em potencial. Neste artigo, provarei que o uso de referências nulas como uma representação em que os valores não existem é uma maneira ruim. Precisamos de um modelo melhor que indique que o valor não existe, em vez de usar referências nulas novamente.
O Java 8 introduziu uma nova classe chamada java.util.Optional<T> , que foi inspirada na linguagem Haskell e na linguagem Scala. Esta classe pode conter um valor arbitrário, conforme mostrado na figura e código abaixo. Você pode pensar em opcional como um valor que pode conter um valor. Se opcional não contiver um valor, ele estará vazio, como mostrado na figura abaixo.
classe pública Computer {Private Opcional <somercard> Sondcard; public Opcional <sorndcard> getSoundcard () {...} ...} public class Soundcard {private opcional <swsb> USB; public Opcional <sB> getusb () {...}} public class USB {public String getversion () {...}} O código acima mostra que um computador pode substituir uma placa de som (a placa de som pode ou não existir). A placa de som também pode incluir uma porta USB. Este é um método de melhoria, e o modelo pode refletir mais claramente que um determinado valor pode não existir.
Mas como lidar com o objeto Optional<Soundcard> ? Afinal, o que você deseja obter é o número da porta USB. É muito simples. A classe opcional contém alguns métodos para lidar se o valor existe. Comparado com referências nulas, a classe opcional força você a lidar se o valor está relacionado, evitando exceções de ponteiro nulo.
Deve -se notar que a classe opcional não substitui referências nulas. Pelo contrário, para facilitar a compreensão da API projetada, quando você vê a assinatura de uma função, pode determinar se o valor a ser transmitido para a função pode não existir. Isso solicita que você abra a classe opcional para lidar com o valor real.
Adote o modo opcional
Depois de dizer tanto, vamos dar uma olhada em algum código! Vamos primeiro olhar como usar opcional para reescrever a detecção de referência nula tradicional. No final deste artigo, você entenderá como usar opcional.
Nome da string = Computer.flatmap (computador :: getoundcard) .flatmap (Soundcard :: getusb) .map (USB :: getversion) .orelse ("desconhecido"); Crie objetos opcionais
Um objeto opcional vazio pode ser criado:
Opcional <somcard> sc = opcional.empty ();
Em seguida, é criar um opcional contendo valores não nulos:
Sondcard Soundcard = new Soundcard (); opcional <sorndcard> sc = opcional.of (Soundcard);
Se a placa de som for nula, a exceção do ponteiro nulo será lançada imediatamente (isso é melhor do que apenas jogá -lo quando você obtém o atributo da placa de som).
Ao usar o Ofnullable, você pode criar um objeto opcional que possa conter referências nulas:
Opcional <somcard> sc = opcional.ofnullable (Sondcard);
Se a placa de som for uma referência nula, o objeto opcional estará vazio.
Processamento de valores em opcional
Agora que existe um objeto opcional, você pode chamar o método correspondente para lidar se o valor no objeto opcional existe. Comparado à detecção nula, podemos usar o método ifpresent (), como este:
Opcional <somcard> Soundcard = ...; Soundcard.ifpresent (System.out :: println);
Dessa forma, não há necessidade de fazer detecção nula. Se o objeto opcional estiver vazio, qualquer informação não será impressa.
Você também pode usar o método isPresent() para verificar se o objeto opcional realmente existe. Além disso, também existe um método get () que retorna os valores incluídos no objeto opcional, se presente. Caso contrário, uma NosuchElementException será lançada. Esses dois métodos podem ser usados juntos, como os seguintes para evitar exceções:
if (Soundcard.ispresent ()) {System.out.println (Soundcard.get ());} No entanto, esse método não é recomendado (não tem melhora em comparação com a detecção nula). Abaixo, discutiremos as maneiras usuais de trabalhar.
Retorna valores padrão e operações relacionadas
Ao encontrar nulo, uma operação regular é retornar um valor padrão, que você pode usar expressões ternárias para implementar:
Sondcard Soundcard = Maybesoundcard! = NULL? Maybesoundcard: new Soundcard ("Basic_Sound_Card"); Se você usar objeto opcional, poderá usar orElse() para substituir. Quando opcional está vazio orElse() pode retornar um valor padrão:
Sondcard Soundcard = Maybesoundcard.orelse (new Soundcard ("Defaut")); Da mesma forma, quando opcional está vazio, OrelSethrow () pode ser usado para lançar exceções:
Sondcard Soundcard = Maybesoundcard.orelsethrow (ilegalStateException :: New);
Use filtro para filtrar valores específicos
Muitas vezes chamamos um método de objeto para julgar suas propriedades. Por exemplo, pode ser necessário verificar se o número da porta USB é um valor específico. Por motivos de segurança, você precisa verificar se o uso médico apontando para USB é nulo e, em seguida, chame getVersion() , como este:
USB USB = ...; if (usb! = Null && "3.0" .equals (usb.getversion ())) {System.out.println ("ok");} Se você usar opcional, pode usar a função de filtro para reescrever:
Opcional <svb> talvezusb = ...; talvezusb.filter (USB -> "3.0" .Equals (USB.GetVersion ()) .IFPRESSE (() -> System.out.println ("ok")); O método do filtro requer um predicado oposto como um parâmetro. Se o valor em opcional existir e satisfazer prever, a função de filtro retornará um valor que satisfaz a condição; Caso contrário, um objeto opcional vazio será retornado.
Use o método do mapa para extrair e converter dados
Um padrão comum é extrair algumas propriedades de um objeto. Por exemplo, para um objeto de cartão de som, pode ser necessário obter seu objeto USB e, em seguida, determinar o número da versão. Geralmente nossa implementação é assim:
if (Soundcard! = null) {USB USB = Soundcard.getusb (); if (usb! = null && "3.0" .equals (usb.getversion ()) {system.out.println ("ok");}} Podemos usar o método do mapa para substituir essa detecção nula e, em seguida, extrair o objeto do tipo de objeto.
Opcional <svb> USB = Maybesoundcard.map (Soundcard :: getusb);
É o mesmo que usar a função do mapa usando o fluxo. O uso do fluxo requer a passagem de uma função como um parâmetro para a função do mapa, e a função passada será aplicada a cada elemento no fluxo. Ao transmitir espaço e tempo, nada acontece.
O valor contido no opcional será convertido pela função passada (aqui está uma função que recebe USB da placa de som). Se o objeto opcional for espaço-tempo, nada acontecerá.
Em seguida, combinamos o método do mapa e o método do filtro para filtrar as placas de som com o número da versão USB, não 3.0.
Maybesoundcard.map (SoundCard :: getUsb) .Filter (USB -> "3.0" .Equals (USB.getversion ()) .IFPRESSE (() -> System.out.println ("ok")); Dessa forma, nosso código começa a se parecer um pouco com o que demos no começo, sem detecção nula.
Passando objeto opcional usando função plana
Agora, foi introduzido um exemplo de como refatorar o código usando opcional. Então, como devemos implementar o seguinte código de maneira segura?
String versão = computador.getSoundcard (). Getusb (). Getversion ();
Observe que o código acima está extraindo outro objeto de um objeto, que pode ser implementado usando a função MAP. No artigo anterior, configuramos um objeto Optional<Soundcard> no computador, e o Sondcard contém um objeto Optional<USB> , para que possamos refatorar o código dessa maneira
String versão = computador.map (computador :: getoundcard) .map (Soundcard :: getusb) .map (USB :: getversion) .orelse ("desconhecido"); Infelizmente, o código acima compila erros, então por quê? A variável de computador é do tipo Optional<Computer> , por isso não tem problema em chamar a função do mapa. No entanto, getSoundcard() retorna um objeto Optional<Soundcard> , que retorna um objeto do tipo Optional<Optional<Soundcard>> . Depois que a função do segundo mapa é chamada, a chamada para getUSB() se torna ilegal.
A figura a seguir descreve este cenário:
A implementação do código -fonte da função do mapa é a seguinte:
public <u> opcional <u> map (function <? super t ,? estende u> mapper) {objects.requiirononnull (mapper); if (! ispresent ()) retorna vazio (); else {return opcional.ofnullable (mapper.apply (value)); }} Pode -se observar que a função do mapa chamará Optional.ofNullable() novamente, resultando no retorno de Optional<Optional<Soundcard>>
Opcional fornece a função FlatMap, projetada para converter o valor do objeto opcional (como uma operação de mapa) e, em seguida, compacte um opcional de dois níveis em um. A figura a seguir mostra a diferença entre objetos opcionais na conversão de tipo chamando mapa e plangmap:
Então, podemos escrever isso:
String versão = computador.flatmap (computador :: getoundcard) .flatmap (Soundcard :: getusb) .map (USB :: getversion) .orelse ("desconhecido"); O primeiro Flatmap garante que o retorno seja Optional<Soundcard> em vez de Optional<Optional<Soundcard>> , e o segundo Flatmap implementa a mesma função para que o retorno seja Optional<USB> . Observe que map() é chamado da terceira vez, porque getVersion() retorna um objeto String em vez de um objeto opcional.
Finalmente, reescrevemos o código feio de verificações nulas aninhadas que começamos a usar, o que é altamente legível e também evita a ocorrência de exceções de ponteiro nulo.
Resumir
Neste artigo, adotamos a nova classe java.util.Optional<T> fornecida por Java 8. A intenção original desta classe não é substituir referências nulas, mas ajudar os designers a projetar melhores APIs. Basta ler a assinatura da função e saber se a função aceita um valor que pode ou não existir. Além disso, o opcional obriga a ativar opcional e depois lidar se o valor existe, o que faz com que seu código evite exceções potenciais de ponteiro nulo.
Ok, o acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.