Entendendo a classe Optional no Java 8



  
Todos os programadores e profissionais relacionados ao desenvolvimento de software sabem que, ao recebermos uma referência de algum objeto, antes de usá-lo devemos checar seu valor, certificando que não esteja nulo.

Na teoria sim, mas na prática...

Na teoria, sempre nos preocupamos com os resultados de se acessar uma área de memória na qual não exista o que se espera. Isso pode significar um desastre total em linguagens de baixo nível como c, e, em linguagens de nível mais alto como java, um grande transtorno. Na prática, não é dessa maneira que a grande maioria dos programadores agem. É comum encontrar blocos de código crítico sem o devido tratamento de erros. 
 
Existem apis externas como Guava, que se prontificam a resolver esse problema, mas é um tanto quanto desgastante pesquisar uma solução, criar mais dependências para resolver problemas como exceções de NullPointerException. Linguagens como Scala proporcionam essa solução de forma nativa.

A classe Optional no Java 8

Na versão mais nova de Java, i. e Java 8, encontramos uma nova classe chamada Optional. O javadoc da mesma é bem claro:

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

Optional é uma classe que encapsula vários métodos úteis para tratar blocos de código crítico que necessitam ser checados.

Abaixo, vamos estudar seus métodos usando exemplos, onde poderemos aplicar essa solução.
O método of é uma fábrica para criar instâncias da classe Optional. O valor passado para o método não deve ser null, caso contrário irá disparar uma exceção NullPointerException:


1
2
3
4
5
6
7
//Criando uma instância de Optional usando a fábrica.

Optional<String> cidade = Optional.of("Varginha");

//A linha abaixo irá disparar uma NullPointerException

Optional<String> nullVal = Optional.of(null);


Semelhante ao método of, ofNullable consegue manejar valores com null. Como exemplo:


1
2
3
//aqui representamos uma instância de Optional contendo o valor
//null.
Optional vazio = Optional.ofNullable(null);


Com as variáveis em mãos, podemos colocá-las à prova e testá-las.



O método isPresent verifica se existe valor embutido na instância de Optional, cidade. Caso exista, a expressão retorna verdadeiro e o método get entra em ação. O método get retorna o valor inicializado pela fábrica of, no caso “Varginha”. Caso não exista valor, uma exceção NoSuchElementException é disparada:



1
2
3
4
5
6
7
//isPresent é usado para checar se existe algum valor embutido na 
//instância de Optional
if (cidade.isPresent()) {

    //get() retorna o valor inicializado em cidade. 
    System.out.println(cidade.get());
}
 
É possível testar um valor e consumi-lo, caso exista. O método ifPresent pode receber uma interface consumidora e executar uma operação sem retornar um valor, como no exemplo abaixo:


1
2
3
//o método ifPresent recebe uma expressão lambda como parâmetro, 
//que consome o valor em cidade, se este for presente
cidade.ifPresent((valor)-> System.out.println(valor.get().toUpperCase()));


orElse vai retornar o valor de cidade, se esta possuir um, caso contrário retornará um valor padrão:


1
2
3
4
5
//orElse irá retornar "Varginha"
System.out.println(cidade.orElse("Existe algum valor!"));

//orElse irá retornar "Não existe valor presente!"
System.out.println(vazio.orElse("Não existe valor presente!"));


orElseGet funciona de maneira semelhante ao método acima. Em orElse nós passamos um valor literal como parâmetro. Agora estamos passando uma interface produtora que possui um método na qual irá gerar o nosso valor padrão:


1
2
3
4
5
//orElseGet irá retornar "Varginha"
System.out.println(cidade.orElseGet(()->"Valor padrão!"));        

//orElseGet irá retornar "valor padrão!"
System.out.println(vazio.orElseGet(()->"Valor padrão!"));


Semelhante ao método orElseGet, orElseThrow também recebe uma interface produtora, mas a expressão lambda que usaremos irá disparar uma exceção quando o valor não for encontrado:


1
2
3
4
5
6
7
 try {
    //ao invés de retornar um valor, orElseThrow dispara uma 
    //exceção caso não exista valor embutido em vazio
    vazio.orElseThrow(ValorAusenteException::new);
 } catch (Throwable throwable) {
    throwable.printStackTrace();
 }


Abaixo a definição da classe ValorAusenteException:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class ValorAusenteException extends Throwable {
    public ValorAusenteException() {
        super();
    }

    public ValorAusenteException(String mensagem) {
        super(mensagem);
    }

    @Override
    public String getMessage() {
        return "Nenhum valor presente";
    }
}


O método map é usado para aplicar um conjunto de operações em uma instância de Optional. As operações são passadas como uma expressão lambda. O método map modifica o valor embutido na instância de Optional pela expressão lambda passada como parâmetro. O valor de retorno da expressão é embutido dentro de uma nova instância de Optional, como segue abaixo o exemplo:


1
2
3
//vai imprimir VARGINHA
Optional<String> caltaCidade = cidade.map((valor) -> valor.toUpperCase());
System.out.println(caltaCidade.orElse("valor inexistente."));


O método flatMap é muito parecido com map. flatMap Se diferencia no tipo de retorno da função de mapeamento. Enquanto a expressão lambda em map pode retornar qualquer valor, em flatMap ela deve ser envelopada em uma instância de Optional:


1
2
3
// imprime VARGINHA
caltaCidade = cidade.flatMap((valor)->Optional.of(valor.toUpperCase()));
System.out.println(caltaCidade.orElse("valor inexistente."));


O método filter é usado para restringir o valor embutido na instância de Optional por passar uma condição a ser aplicada no valor. Se o valor é presente, e o valor corresponde ao predicado passado, filter vai retornar uma instância de Optional com o devido valor, caso contrário retornará uma instância vazia de Optional. Segue o exemplo:


1
2
3
4
5
6
7
8
9
Optional<String> extensaCidade = cidade.filter((valor)->valor.length() > 5);
//imprime Varginha
System.out.println(extensaCidade.orElse("Cidade possui menos de 5 caracteres"));

//outro exemplo mostrando como a condição pode falhar
Optional<String> outraCidade = Optional.of("Var");
Optional<String> curtaCidade = outraCidade.filter((valor)-> valor.length() > 5);
//falha: imprimindo "outraCidade possui menos de 5 caracteres"
System.out.println(curtaCidade.orElse("outraCidade possui menos de 5 caracteres"));


Seria a classe Optional realmente útil?


Com certeza. A classe Optional possibilita um desenho consistente de tratamento de erros em blocos de código crítico. Criar um bom desenho é uma tarefa que pode levar um tempo considerável e custar a sua produtividade. Possuindo em mãos a solução nativa, podemos nos focar em solucionar o problema de maneira eficaz.

Fontes:

Comentários