Chega de NullPointerException, trabalhe com o java.util.Optional!

Já vimos aqui no blog as principais novidades do Java 8, detalhes da nova API de datas e como aplicar algumas dessas novidades em nosso dia a dia. Mas há muito o que explorar nas diversas introduções dessa nova versão da linguagem.

Quem nunca escreveu um código parecido com esse?

List<Matricula> matriculas = turma.getMatriculas();

for (Matricula matricula : matriculas) {
	System.out.println(matricula.getCurso().getNome());
}

Tudo parece certo, mas esse código pode ser bastante problemático. A simples ausência de algum desses valores pode resultar no tão frequente NullPointerException. Claro, você pode contornar esse problema de diversas formas, como por exemplo inicializar suas listas ou sempre verificar que os valores são diferentes de null antes de fazer qualquer operação com eles.

Nosso código pode ficar parecido com:

List<Matricula> matriculas = turma.getMatriculas();

for (Matricula matricula : matriculas) {
	if (matricula.getCurso() != null) {
		System.out.println(matricula.getCurso().getNome());
	}
}

Nada mal, mas e os outros atributos? A quantidade de ifs perdidos pelo nosso código para fazer esse tipo de verificação só tende a aumentar, isso faz com que ele fique cada vez mais ilegível e difícil de manter. E esse nem é o pior dos problemas, esquecer de fazer uma verificação como essa é um risco muito grande… em algum momento isso vai acontecer e você receberá um NullPointerException.

Como resolver o problema? A mais nova versão do Java nos prove uma forma muito mais interessante de representar esses atributos que podem ou não estar presentes em nosso código, utilizando o java.util.Optional.

Você pode pensar em um Optional como uma classe que pode ou não conter um valor não nulo. Repare como fica o código da classe Matricula declarando o atributo Curso como Optional:

public class Matricula {

	private Optional<Curso> curso = Optional.empty();

	// outros atributos, getters e setters
}

Utilizamos o factory method empty para criar e inicializar o atributo com um Optional vazio. Há outras formas de se criar um Optional, você pode por exemplo utilizar seu método of:

Optional<Curso> cursoOpcional = Optional.of(algumCurso);

Mas é importante saber que caso o valor da variável algumCurso seja null, esse código irá lançar um NullPointerException. Para evitar isso podemos utilizar o método ofNullable:

Optional<Curso> cursoOpcional = Optional.ofNullable(algumCurso);

Agora que o método getCurso retorna um Optional, precisamos chamar seu método get para recuperar o curso que esse Optional está guardando:

for (Matricula matricula : matriculas) {
	if (matricula.getCurso() != null) {
		System.out.println(matricula.getCurso().get().getNome());
	}
}

Mas dessa forma, se o curso não existir vamos receber um java.util.NoSuchElementException: No value present. Como prevenir isso? Uma das possíveis formas seria verificando se o valor desse Optional está presente, algo como:

for (Matricula matricula : matriculas) {
	Optional<Curso> cursoOpcional = matricula.getCurso();
	if(cursoOpcional.isPresent()) {
		System.out.println(cursoOpcional.get().getNome());
	}
}

Mas isso ainda deixa nosso código muito verboso! Que tal fazer algo como:

for (Matricula matricula : matriculas) {
	matricula.getCurso()
		.ifPresent(c -> System.out.println(c.getNome()));
}

Bem mais interessante, não acha? Esse é um dos vários métodos funcionais que essa classe possui. O método ifPresent recebe um Consumer que pode ser traduzido em uma expressão lambda c -> System.out.println(c.getNome()). Mas note que você não precisa se preocupar com nenhum desses detalhes, não importa o nome da interface funcional por traz desse código, não importa o nome de seu único método, tudo que importa nesse momento é a expressão: tenho um curso e quero imprimir seu nome.

Se você está se perguntando, podemos sim fazer o for da maneira nova! Utilizando o já conhecido forEach:

matriculas.forEach(m -> {
	m.getCurso()
		.ifPresent(c -> System.out.println(c.getNome()));
});

Outro método interessante da classe Optional é o orElseThrow. Caso neste contexto o curso seja obrigatório, podemos fazer algo como:

matriculas.forEach(m -> {
	Curso curso = m.getCurso()
		.orElseThrow(IllegalArgumentException::new);
});

Quer mais? Considere que a classe Curso possui uma descrição (String). É comum escrever um código parecido com esse para mostrar a descrição ou alguma outra mensagem caso ela não esteja presente:

matriculas.forEach(m -> {
	m.getCurso().ifPresent(c -> {
		if (c.getDescricao() != null){
			System.out.println(c.getDescricao());
		} else {
			System.out.println("sem descrição");
		}
	});
});

Mas agora com Optional podemos escrever da seguinte forma:

matriculas.forEach(m -> {
	m.getCurso().ifPresent(c -> {
		System.out.println(c.getDescricao().orElse("sem descrição"));	
	});
});

O método orElse nos retorna o curso caso presente, ou pelo contrário o valor passado como argumento.

Para imprimir apenas quando a descrição existir e não for vazia, poderíamos escrever um código parecido com:

matriculas.forEach(m -> {
	m.getCurso()
		.ifPresent(c -> {
			Optional<String> descricao = c.getDescricao();
			if (descricao.isPresent() && !descricao.get().isEmpty()) {
				System.out.println(descricao.get());
			}
		});
});

Resolvemos o problema, mas o código não está nem um pouco legível!

Para evitar esse encadeamento desnecessário, podemos utilizar o método map para fazer uma projeção do curso para sua descrição. Isso mesmo, assim como o Stream, um Optional também possui o método map! Vamos ver o resultado:

matriculas.forEach(m -> {
	Optional<Optional<String>> map = 
			m.getCurso().map(Curso::getDescricao);
});

Pois é… o map vai nos retornar um Optional>. Ainda não é o que estamos esperando. Se você já conhece a API de Streams ou programa com alguma linguagem funcional, já deve ter pensado em uma solução. Sim, o Optional também possui o método flatMap! Utilizando esse método podemos achatar o Optional> para um Optional com a descrição:

matriculas.forEach(m -> {
	Optional<String> flatMap = 
		m.getCurso().flatMap(Curso::getDescricao);
});

Estamos quase lá. Para imprimir a descrição apenas quando ela não for vazia, podemos utilizar o método filter seguido pelo ifPresent que já conhecemos:

matriculas.forEach(m -> {
		m.getCurso()
			.flatMap(Curso::getDescricao)
			.filter(d -> !d.isEmpty())
			.ifPresent(System.out::println);
});

Está pronto! Muito mais interessante que a solução anterior, não acha?

Há muito mais o que aprender sobre as novas APIs da linguagem. Você pode ver mais das novidades em nosso livro e no mais novo curso online do Alura.

38 Comentários

  1. Icaro 30/07/2014 at 09:05 #

    No fundo, o uso de expressão lambda ainda faz os ifs além de fazer a escrita do código, embora mais elegante, ainda mais complexa.
    O groovy resolve isso de uma forma MUITO mais elegante e simples:
    -> chamada opcional (devolve null): casa?.endereco?.rua
    -> parâmetros opcionais em métodos
    -> parâmetros em formato de mapa
    -> ifs diretamente sobre inteiros string ou qualquer object (true ou false vai depender da convenção para cada tipo. Ver a spec)
    -> expressão lambda de uma forma muito mais sucinta e simples

  2. Rodrigo Turini 30/07/2014 at 10:04 #

    Oi Icaro! A forma do groovy é realmente bastante interessante! Mas são linguagens bem diferentes, com história, caminhos e objetivos diferentes. Na minha opinião o Optional foi um avanço enorme para a linguagem Java. um abraço

  3. Rafael 30/07/2014 at 12:39 #

    Ou seja, no fim das contas é quase a mesma coisa.

    Ex.:
    private Optional curso = Optional.empty();
    ==
    private Curso curso = new Curso();

  4. Rodrigo Turini 30/07/2014 at 13:03 #

    Oi Rafael! A diferença é que o Optional vai além da inicialização dos atributos.

    Dê uma olhada na quantidade de métodos funcionais que essa classe possui:

    http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

    Você evita os diversos ifs com valor != null ou o risco constante de receber um NPE.

  5. Henrique 30/07/2014 at 14:10 #

    Realmente, conforme o baiano disse ali em cima, depois que vc aprende Groovy a linguagem/sintaxe Java fica muito pra trás, mesmo com essas novidades do Java 8.

  6. Paulo Freitas 31/07/2014 at 08:56 #

    Com certeza o Groovy tem seus trunfos, mas é bem como o Turini disse, são ideias, histórias e objetivos diferentes. Por exemplo, quanto a visibilidade e encapsulamento o Groovy deixa as portas abertas. O mesmo sobre captura de exceptions, não obriga que sejam tratadas. Daí podemos trazer Scala para o jogo… hahahaha..

    Mas, novamente concordando com o Turini, é um grande avanço para o nosso querido tiozão. Ele tem muitas coisas boas e na minha opinião incentiva muito o design da solução. O problema é quando se quer executar operações triviais em collections ou coisas similares. Porém o Java 8 trás boas opções para este tipo de tarefa.

  7. Pablo 31/07/2014 at 09:45 #

    Groovy é uma linguagem dinâmica e isso acarreta em uma série de desvantagens (e vantagens também!)

  8. Thiago 31/07/2014 at 16:45 #

    Lendo esses posts sobre Java 8, chego a conclusão que não sei mais Java…rs.
    Correr atrás do prejuizo.

  9. Raphael Lacerda 01/08/2014 at 14:55 #

    Muito bom! Faz um select no banco, ao invés de retornar null quando não encontrou nenhum resultado, retorne um Optional.

    Um outro ponto de vista

    http://java.dzone.com/articles/java-8-optional-whats-point

  10. Fausto 02/08/2014 at 09:34 #

    Oi. Bem legal teu artigo eu não li completamente mas vi todos os exemplos e achei bem interessante fazer essa programação defensiva nativa. Qndo programava em java eu odiava fazer isso, e tinha libs que resolviam esse problem com uso de predicados. Nao sabia q o java 8 tinha incorporado esse approach!
    PArabéns

  11. Leo Costa 03/08/2014 at 00:10 #

    Bacana hein! Considero a redução da quantidade de linhas de código, é uma evolução!

  12. Gil Gomes 04/08/2014 at 08:48 #

    Nada como conhecer um pouco da API.

  13. Daniel Augusto 04/08/2014 at 13:34 #

    Ainda não fiquei empolgado com este recurso do J8, pois não traz nenhuma mudança prática na forma de trabalhar.
    Talvez com o costume de ver muitos código com isso, mude de ideia.

    Na minha opinião, a necessidade de continuar fazendo a verificação com o ifPresent é uma maneira preguiçosa de fazer o != null.

    Se não utilizarmos o ifPresent iremos receber uma exceção do mesmo jeito que antes, só que ao invés de receber uma NPE receberemos java.util.NoSuchElementException. Qual a diferença prática em receber essa exceção ao invés da conhecida NPE?

    Acho que seria uma avanço se ficasse como é em Groovy: casa?.endereco?.rua

  14. Thiago Baptista 11/08/2014 at 09:47 #

    Recursos “inovadores” como esse são a prova de o quanto Java é uma linguagem falida.

    Um projetista de API que acha que fazer um código desses é uma boa idéia é prova de o quanto o Java burocratizou irreversivelmente toda uma geração de desenvolvedores.

    “Usar if-else é muito ‘verboso’! Verificar se é nulo antes de chamar um método é muito ‘verboso’! Então, vamos criar um objeto que usa generics através de um método estático contrutor e usar if-else pra verificar se o objeto é vazio, mas dessa vez COM LAMBDAS!!!”

    Depois a galera de Python vem nos sacanear e a gente não consegue nem se defender…

  15. Rodrigo Turini 11/08/2014 at 11:57 #

    Oi Thiago! Obrigado por compartilhar a sua opinião. Quem sabe com o uso/passar do tempo ela mude, nós já estamos usando em nossos projetos e (pelo menos pra mim) o ganho foi evidente. Um abraço

  16. Mauricio 12/08/2014 at 08:30 #

    Acredito que o amigo Thiago Baptista não gostou muito da idéia pelo exemplo do post, que apesar de ser bom, é muito simples e parece que a classe ficou meio inútil, parecendo mais uma gambiarra do que uma boa solução.
    Essa classe é baseada no Guava (API do google) ele fez muito sucesso por deixar códigos GRANDES menos verbosos, por isso acabou indo para dentro do Java.
    Na minha humilde opinião ela vai acabar mais atrapalhando do que ajudando, pois vão usar discriminadamente em qualquer lugar deixando o código muito pior na maioria das situações, mas se usarem em situações corretas, com certeza haverá um ganho enorme. 🙂

  17. Marcelo 13/08/2014 at 12:53 #

    Sinceramente. A capacidade de recriar a roda é infinita.

  18. Thiago Alves 13/08/2014 at 18:14 #

    Lendo os comentários até parece que a intenção foi comparar java com qualquer outra coisa… sinceramente isso que desanima dividir experiência e conteúdo na web, simplesmente aparece um monte de sabe tudo de tudo que não acrescentam em nada, é como jogar “pérolas aos porcos”. Parabéns pelo post Rodrigo Turini.

  19. Dionízio Inácio 14/08/2014 at 10:01 #

    Excelente post Rodrigo Turini. Agora discutir linguagens é detalhe, cada uma na sua realidade e com seu público ou projeto definido. Talvez não aplique em todo o código, mas é um avanço quando se diz que vai diminuir linhas.

  20. Rodrigo Turini 14/08/2014 at 10:25 #

    Oi Pessoal, obrigado pelos feedbacks. Sempre há comparação entre linguagens, isso talvez seja inevitável… mas uma perspectiva interessante é comparar o novo recurso com a forma que era feito antes, em Java. Acho bem justo resistir um pouco quando se trata de uma mudança como essa, mas talvez com o uso prático essa impressão mude, vamos ver. um abraço

  21. Denilson Telaroli 15/08/2014 at 16:54 #

    Independente de outras linguagens serem melhores ou não que o Java, só quem usa o Java no dia a dia sabe da importância desta “inovação”.

  22. Otávio 22/08/2014 at 14:48 #

    O foco do Optional não é apenas evitar NullPointerException. Acredito que a intenção do excelente post do Rodrigo foi dar uma pincelada de uma funcionalidade muito legal do Java. E que pode nos ajudar em muitos casos.

    Um exempo para ilustrar algo mais amplo é: tenho um objeto source, e se esse objeto for NULL quero fazer algo, mas se ele for NOT NULL quero fazer outra coisa.

    Eu poderia fazer isso:

    if (source == null) {
    return algumaCoisa;
    } else {
    transforma meu source em alguma coisa
    return source transformado;
    }

    Ou assim usando Optional:

    Optional.ofNullable(source).orElseGet(translateToAnotherObject()).collect(toList());

    Ou seja, a leitura do código fica mais fluente do que alguns blocos aninhados com ifs. E claro, esse é um exemplo simples.

    Usando Streams a gente nota que o uso do Optional ajuda mais ainda. No JPA, quando queremos retornar primeiro elemento encontrado, ou NULL caso a coleção for vazia.

    List x = em.getResultList().isEmpty();
    return x.isEmpty() ? null : x.get(0);

    Com Optional:

    return em.getResultList().stream().first().orElse(null);

  23. Wesley Martins 08/10/2014 at 09:30 #

    Galerinha…

    Gostei, mas ainda não aprovei 100%.
    Concordo com os comentários que é uma evolução, mas ainda não chegamos lá.
    Pelo amor de Deus, que no JAVA 9 “NullPointerException is dead”.

  24. Fiofó 22/10/2014 at 11:51 #

    Isso aí embaixo é uma bizarrice sem igual… if else é bom gente… só é ruim se usar porcamente

    Optional.ofNullable(source).orElseGet(translateToAnotherObject())

  25. Rodrigo Turini 22/10/2014 at 11:57 #

    Oi! É uma impressão válida, claro. Talvez ela mude com o passar do tempo.

    Além disso é importante observar que a API de Optional vai bem além do if/else.

  26. Fiofó 22/10/2014 at 17:51 #

    Turini, é a terceira vez que você fala algo do tipo “talvez a opinião mude com o passar do tempo”.

    Você quer “defender” o Java 8 (ainda que de forma política)? Essa é a impressão que você está passando.

    Vamos ser sinceros, o Java está ultrapassado, outras linguagens estão crescendo. Agora If … else virar closure encadeada é o fim da picada… e olha isso:

    private Optional curso = Optional.empty();

    Um abuso completo do conceito de generics! O JCP fico u doido, mergulhado no mar de generics. Fora a semântica que é horrível né? Meu objeto é um Optional ou um curso?

    Fala sério pessoal, Java 8, nesse ponto, foi uma decepção.

  27. Rodrigo Turini 22/10/2014 at 18:52 #

    Oi, na verdade essa é minha opinião sincera. Assim como foi difícil aceitar o generics no inicio (e talvez seja até hoje), é natural não reagir bem com mudanças tão drásticas em uma linguagem (seja ela Java ou qualquer outra). Não vejo Java como uma linguagem ultrapassada, nem acho a semântica horrível… muito pelo contrário. Mas meu objetivo aqui não é discutir o gosto, e sim apresentar alguns dos novos recursos.

  28. Paulo Silveira 22/10/2014 at 18:54 #

    O if-else não virou closure encadeada. o uso do Optional com essa estrutura de if-else vai ser usado quando você quis deixar claro que ali pode ter um null. Se você não usar o Optional, você *nunca* vai obrigar o programador a usar o if-else pra tomar cuidado com aquela referência. Usando o Optional, você obriga. Diferença gritante.

  29. Diego Monteiro 10/12/2014 at 03:00 #

    Parabéns pelo Post Rodrigo, bom sinto que a maioria não está pronta para esta mudança ou tinha expectativas de uma mudança mais elegante. Bom na minha opinião o Optional realmente traz inovações. Estou adaptando alguns dos meus projetos que utilizam Stream de arquivos e conexões que literalmente geram ocasionalmente NullPointers, acredito que com a utilização do Optional irei me policiar no tratamento. Sugiro que quem esteja decepcionado com a solução apresentada no Java 8 que opine com maior riqueza de detalhes e apresente uma solução mais elegante.

  30. Rodrigo Turini 10/12/2014 at 10:16 #

    Valeu Diego!

  31. Ilson 20/07/2015 at 13:56 #

    Está bem claro que os projetistas do Java 7 recusaram o “Elvis operator”, que é a funcionalidade do Groovy fazer “matricula.curso?.nome” para evitar NPE. No Java 8 acharam a solução! Claro que o Optional é mais verboso que o Elvis, mas tem a vantagem bem explicada pelo Paulo Siveira: Obriga o programador a tratar objetos que possam ser nulos. Isto é ótimo para projetos grandes, onde há muitas variáveis e muitos programadores trabalhando. Ponto positivo para linguagem Java! Este é um dos motivos que o Java continua sendo escolhido para novos projetos mesmo já existindo linguagens que aparentam ser mais produtivas

  32. André Franco 27/07/2015 at 10:53 #

    Uma dúvida.
    No caso de utlizar o Optional evitaria que fosse realizado validação para todos os atributos da classe ? Não entendi a parte de com o uso do Optional torna-se obrigatório a validação, o simples fato de instanciar o objeto já o faz necessário sua utilização ?
    O uso do lambda me deixou bastante confuso.

  33. Rodrigo Turini 27/07/2015 at 11:02 #

    Oi André, tudo bem? O ponto é que, se um método retorna um `Optional`, você sabe que aquele retorno pode ou não existir. Portanto a própria semântica desse recurso vai te induzir a validar, chamando um `ifPresent` com lambda, ou algo assim. E se o seu código respeitar bem esse princípio, você saberá que quando retorna um objeto, ele sempre estará presente, caso contrário será opcional. Leva um tempo pra acostumar, principalmente com a sintaxe dos lambdas.

  34. Agnaldo 04/09/2015 at 12:42 #

    A velha história da comparação entre linguagens…

    VB x DELPHI??? JAVA x SCALA??? e por ai vai…

    É como comparar o fato de eu ter uma ferrari e sair zombando de quem usa um fusquinha.

    Cuidado gente, já vi fusquinha bater um porche numa arrancada.

    Cada uma das linguagens tem seus pontos fortes e fracos também.

    O bom programador sabe usar os fortes e se contornar os fracos.

    Ao invés de ficar tentando provar que esta ou aquela linguagem é melhor, prove para você mesmo que você é bom e extraia o máximo da ferramenta.

    O bom profissional não é aquele que tem a melhor ferramenta, mas aquele que sabe usar a ferramenta que tem em mãos.

  35. Leo 07/12/2017 at 10:41 #

    Ótimo. Em vez de ter nullPointerException eu tenho NoSuchElementException. Muito bom!

  36. Rodrigo Turini 09/12/2017 at 01:37 #

    oi, Leo

    tem razão, no final acabamos correndo o risco de NoSuchElementException.

    mas perceba que pra recebermos essa exception, precisaríamos *intencionalmente* chamar o método .get() sem tratar o caso de valor vazio. Em outras palavras, é mais fácil de evitar já que a API te passa essa informação… você só recebe o erro se ignorar a possibilidade.

    Com o null a história é outra. Nem sempre o objeto é criado em um ambiente controlado, e você precisa *lembrar* de conferir se é null ou nao.

Deixe uma resposta