[Pesquisar este blog]

domingo, 10 de dezembro de 2017

Java 9::Melhorias no Optional

NullPointerException: quem nunca ficou realmente chateado ao ver, inesperadamente, esta exceção que atire a primeira crítica! Como toda exceção não prevista, é provável que sua ocorrência cause o encerramento anormal e abrupto do programa, algo muito constrangedor quando é o usuário que presencia este fato.
É notório que a famigerada exceção NullPointerException é lançada quando ocorre uma tentativa de acessar um campo ou acionar um método por meio de uma variável de referência inadequadamente inicializada, isto é, sem exista uma instância de objeto na variável referência utilizada. Traduzindo em miúdos: a variável de referência contém apenas null, que sinaliza a ausência de um objeto.

Todo programador sabe que isso (a tentativa de usar um objeto inexistente) é uma operação ilegal, ou seja, a questão aqui não é o uso inadvertido de variáveis de referência não inicializadas, até mesmo porque muitos dos IDE disponíveis, como o Eclipse, sinalizam, até com certo estardalhaço, que variáveis não inicializadas estão em uso; mas quando operações realizadas sobre outros objetos retornam referências válidas.

O problema mais comum, que dá origem às ocorrências de NullPointerException, é o acionamento de um método que, eventualmente, possa retornar um resultado null. Até a versão 7 do Java, a solução para evitar esta exceção era testar as variáveis de referências duvidosas, fazendo seu uso apenas quando diferentes de null. Mas isto polui o código.

A versão 8 do Java trouxe a classe Optional para solucionar este problemas.

Optional<T>

Com nítida inspiração nas linguagens de programação Scala, Haskell e Guava, a classe genérica Optional<T> funciona como uma classe genérica (um template portanto) para encapsular referências do tipo T que podem ser nulas. Seu uso permite que o projetista de uma API possa, mais claramente, indicar que um valor nulo pode ser retornando ou passado para um método, reduzindo a necessidade de consulta à documentação desta operação.

Objetos do tipo Optional<T> são como contêineres (i.e., containers) que podem armazenar um valor de qualquer tipo T ou apenas null. A classe Optional também provê alguns métodos úteis que podem eliminar a verificação explícita da presença de null.

Considere o trecho de código que segue:

String parametro = ????; // inicialização do parâmetro, que pode ou não ser nulo
System.out.println("Parâmetro está definido? " + (parametro!=null ? true : false) );
if (parametro == null) {
   System.out.println("Parâmetro: [sem definição]" );
} else {
   System.out.println("Parâmetro: " + parametro);
}
if (parametro != null) {
   System.out.println("programa " + parametro);
} else {
   System.out.println("programa");
}

Como a variável do tipo String denominada parametro pode ou não ser nula, para que não ocorram erros decorrentes de seu uso indevido, o trecho de programa verifica explicitamente (de maneiras diferentes) quando parametro é ou não nulo, avolumando o trecho e dificultando sua legibilidade. 

O uso da classe Optional<T> e seus métodos permite simplificar código como este, com uma construção que pode ser como segue, onde o parametro é nulo:

Optional<String> parametro = Optional.ofNullable(null);
System.out.println("Parâmetro está definido? " + parametro.isPresent() );
System.out.println("Parâmetro: " + parametro.orElseGet(() ‐> "[sem definição]") );
System.out.println(parametro.map(p ‐> "programa " + p).orElse( "programa" ) );

O método isPresent() retorna true quando a instância de Optional<T> contém um valor não nulo e false quando nulo. O método orElseGet() provê um mecanismo alternativo, baseado numa expressão lambda que se comporta como um Producer, que provê um valor quando Optional<T> contém nulo. Já o método map() transforma o valor corrente de Optional<T> e retorna uma nova instância. O método orElse() é semelhante a orElseGet(), mas toma um valor default ao invés de uma função de produção.

A saída deste programa, executado sem qualquer erro, é:

Parâmetro está definido? false
Parâmetro: [sem definição]
programa

Um outro exemplo seria o mesmo trecho de programa, mas com um valor provido para o parâmetro.

Optional<String> parametro = Optional.of("--check");
System.out.println("Parâmetro está definido? " + parametro.isPresent() );
System.out.println("Parâmetro: " + parametro.orElseGet(() ‐> "[sem definição]") );
System.out.println(parametro.map(p ‐> "programa " + p).orElse( "programa" ) );

Agora a saída deste programa, que continua sendo executado sem erros, é:

Parâmetro está definido? true
Parâmetro: --check
programa --check

Este exemplo simples permite observar que o uso do parâmetro, nulo ou não, é feito corretamente e de maneira segura, mesmo sem a presença de testes explícitos realizados com diretivas if.


Melhorias em Optional<T>

A versão 9 do Java traz algumas melhorias, pequenas, mas bastante úteis.

O novo método ifPresentOrElse() verifica se um valor está presente na instância de Optional<T>, conduzindo a ação indicada com o valor, ou realizando outra para a situação de vazio (conteúdo null):

public void ifPresentOrElse(Consumer<T> action, Runnable emptyAction);

Dito de outra maneira, este método codifica um padrão comum, no qual se deseja executar uma ação quando Optional<T> contém um valor, ou uma ação diferente quanto tal valor está ausente.

Considere o trecho que segue:

String parametro = getParameterByPosition(position);
if (parametro != null) {
   executeParameterizedAction(parametro);
} else {
   executeDefaultAction();
}

Com o uso do novo método de Optional<T>, o trecho equivalente seria:

Optional<String> parametro = Optional.of(getParameterByPosition(position));
parametro.ifPresenteOrElse(
    this::executeParameterizedAction,
    ()-> executeDefaultAction() );


Ou ainda mais simples, se o método getParameterByPosition(int) retornasse diretamente um Optional<String>:

getParameterByPosition(position).
   ifPresenteOrElse(this::executeParameterizedAction,
                    ()-> executeDefaultAction() );


Outro método interessante é or(), que toma uma função que cria um Optional<T> como argumento. Sua assinatura é:

public Optional<T> or(Supplier<Optional<T>> supplier);

Se o valor está presente, retorna um Optional<T> que encapsula tal valor, senão, retorna um outro Optional produzido pela função de geração. Isto é útil para encadear duas ou mais funções que retornam Optional, de modo que o primeiro Optional contendo um valor não nulo seja retornado.

Por exemplo:

public Optional<String> getRecord(int recordNumber) {
   return findInDatabase(recordNumber).or(()->findInFileSystem(recordNumber));
}

Este método retorna o resultado de findInDatabase(int), quando este não é nulo, ou ou resultado de findInFileSystem(int) quando não encontrado pelo primeiro método.

Conclusões

As melhorias em Optional<T> são simples, mas auxiliam na construção de programas melhores, principalmente livres da desagradável exceção NullPointerException. É claro que métodos cuja implementação ainda retorne null deverão ser modificados para retornar objetos Optional<T> ou, ao menos, ter seus resultados encapsulados em objetos Optional<T> para que tais benefícios possam ser percebidos. Por outro lado, a inclusão de Optional<T> no Java 8, e das melhorias comentadas no Java 9, não farão qualquer mágica, exigindo alguma manutenção, mas que com certeza, valerá o esforço.


Para Saber Mais