Selecione um único elemento
Intuitivamente, selecionar um único elemento é definitivamente mais fácil do que selecionar vários elementos, mas existem alguns problemas aqui. Vejamos primeiro qual é o problema da abordagem geral e, em seguida, veremos como usar expressões lambda para resolvê-lo.
Vamos primeiro criar um novo método para encontrar um elemento que comece com uma letra específica e depois imprimi-lo.
Copie o código do código da seguinte forma:
public static void pickName(
final List<String> nomes, final String inicialLetra) {
String nome encontrado = null;
for(String nome: nomes) {
if(nome.startsWith(letrainicial)) {
nome encontrado = nome;
quebrar;
}
}
Esse método é tão fedorento quanto o caminhão de lixo que acabou de passar. Primeiro criamos uma nova variável foundName e depois a inicializamos como nula - esta é a fonte do fedor. Temos que verificar se é nulo, caso contrário, uma NullPointerException ou uma resposta de erro será lançada. Também usamos um iterador externo para percorrer a lista. Se encontrarmos o elemento que desejamos, teremos que sair do loop, o que aumenta o cheiro original: paranóia de tipo básico, estilo imperativo, mutabilidade, tudo ganha vida. Assim que sairmos do loop, temos que verificar os resultados antes de imprimir. Na verdade, uma tarefa tão pequena requer um código tão longo.
Vamos reanalisar esta questão. Queremos apenas ser capazes de selecionar o primeiro elemento correspondente e lidar com segurança no caso em que tal elemento não exista. Vamos reescrever este método pickName usando uma expressão lambda.
Copie o código do código da seguinte forma:
public static void pickName(
final List<String> nomes, final String inicialLetra) {
final Opcional<String> encontradoNome =
nomes.stream()
.filter(nome ->nome.startsWith(letrainicial))
.findPrimeiro();
System.out.println(String.format("Um nome começando com %s: %s",
startLetter, foundName.orElse("Nenhum nome encontrado")));
}
Algumas funções poderosas do JDK tornam esse código muito conciso. Primeiro, usamos o método filter para obter todos os elementos que atendem às condições e, em seguida, usamos o método findFirst da classe Stream para selecionar o primeiro elemento da coleção retornada. Este método retorna um objeto Opcional, que é o desodorizador oficialmente certificado para variáveis nulas em Java.
A classe Opcional é muito útil, você não precisa se preocupar se o resultado existe ou não. Isso nos salva do problema de exceções de ponteiro nulo e deixa mais claro que nenhum resultado é um resultado possível. Através do método isPresent(), podemos saber se o resultado existe. Se quisermos obter o valor do resultado, podemos usar o método get(). Também podemos definir um valor padrão para ele usando (o nome deste método irá chocá-lo) o método orElse, assim como no código anterior.
Usamos a coleção de amigos que usamos antes para verificar nosso método pickName.
Copie o código do código da seguinte forma:
pickNome(amigos, "N");
pickNome(amigos, "Z");
Este código seleciona o primeiro elemento correspondente e imprime uma mensagem amigável se não for encontrado.
Copie o código do código da seguinte forma:
Um nome que começa com N: Nate
Um nome começando com Z: Nenhum nome encontrado
A combinação do método findFirst() e da classe Optinal reduz a quantidade de nosso código e parece boa. Mas as funções da classe Opcional são muito mais que isso. Por exemplo, além de fornecer um valor padrão quando o objeto não existe, você também pode usá-lo para executar um trecho de código, ou uma expressão lambda, se o resultado existir, como este:
Copie o código do código da seguinte forma:
foundName.ifPresent(nome -> System.out.println("Olá " + nome));
Comparado com o código imperativo de seleção do primeiro nome correspondente, o estilo elegante e funcional do fluxo parece melhor. Mas há muito o que fazer nesta versão do fluxo de chamadas? Claro que não, esses métodos são muito inteligentes e funcionam sob demanda (exploraremos isso em profundidade em Avaliação Preguiçosa de Streams na página 113).
O exemplo de seleção de um único elemento mostra funções mais poderosas da biblioteca JDK. Vamos dar uma olhada em como as expressões lambda podem encontrar um valor desejado com base em uma coleção.