Conversão de lista
Converter uma coleção em uma nova coleção é tão simples quanto iterá-la. Suponha que queiramos converter os nomes de uma lista para letras maiúsculas. Vamos dar uma olhada em alguns métodos de implementação.
Uma string em Java é imutável, portanto não pode ser alterada. Podemos gerar novas strings para substituir os elementos originais da lista. No entanto, se você fizer isso, a lista original desaparecerá; há outro problema. A lista original também pode ser imutável, como a gerada por Arrays.asList(), portanto, modificar a lista original não funcionará. Outra desvantagem é que é difícil operar em paralelo.
Gerar uma nova lista em letras maiúsculas é uma boa escolha.
À primeira vista, isto pode parecer um conselho fraco; o desempenho é uma questão que nos preocupa. Surpreendentemente, a programação funcional costuma ter melhor desempenho do que a programação imperativa, como discutimos em Questões de desempenho na página 153.
Vamos começar usando este conjunto para gerar um novo conjunto de letras maiúsculas.
Copie o código do código da seguinte forma:
final List<String> uppercaseNames = new ArrayList<String>();
for(String nome: amigos) {
nomesmaiúsculos.add(nome.toUpperCase());
}
No código imperativo, primeiro criamos uma lista vazia e depois a preenchemos com nomes maiúsculos, inserindo um de cada vez enquanto percorremos a lista original. Para melhorar para uma versão funcional, nosso primeiro passo pode ser considerar a substituição do loop for pelo iterador interno forEach mencionado ao percorrer a lista na página 19, conforme mostrado no exemplo a seguir.
Copie o código do código da seguinte forma:
final List<String> uppercaseNames = new ArrayList<String>();
amigos.forEach(nome -> nomesmaiúsculos.add(nome.toUpperCase()));
System.out.println(nomes maiúsculos);
Usamos um iterador interno, mas também temos que criar uma nova lista e inserir elementos nela. Podemos melhorar ainda mais.
Usando expressões lambda
Existe um método de mapa na interface Stream recentemente introduzida, que pode nos ajudar a evitar variabilidade e fazer o código parecer mais conciso. O Steam é um pouco como um iterador de coleção, mas também oferece funções fluentes. Utilizando os métodos desta interface, podemos combinar uma série de chamadas para que o código seja lido como a ordem que descreve o problema, tornando-o mais legível.
O método map do Steam pode ser usado para converter uma sequência de entrada em uma sequência de saída – o que é uma combinação perfeita para o que queremos fazer.
Copie o código do código da seguinte forma:
amigos.stream()
.map(nome -> nome.toUpperCase())
.forEach(nome -> System.out.print(nome + " "));
System.out.println();
Todas as coleções no JDK8 suportam este método de stream, que encapsula a coleção em uma instância do Steam. O método map chama a expressão lambda ou bloco de código especificado para cada elemento no Stream. O método map é muito diferente do método forEach. forEach simplesmente executa uma função especificada nos elementos da coleção. O método map coleta os resultados da execução da expressão lambda e retorna um conjunto de resultados. Finalmente imprimimos todos os elementos usando o método forEach.
Os nomes na nova coleção estão todos em letras maiúsculas:
Copie o código do código da seguinte forma:
BRIAN NATE NEAL RAJU SARA SCOTT
O método map é muito adequado para transformar uma coleção de entrada em uma nova coleção de saída. Este método garante que as sequências de entrada e saída tenham o mesmo número de elementos. No entanto, os elementos de entrada e saída podem ser de tipos diferentes. Neste exemplo, nossa entrada e saída são coleções de strings. Podemos passar um trecho de código ao método map para que ele retorne, por exemplo, o número de caracteres contidos no nome. Nesse caso, a entrada ainda é uma sequência de strings, mas a saída é uma sequência de números, como a seguir.
Copie o código do código da seguinte forma:
amigos.stream()
.map(nome -> nome.comprimento())
.forEach(contagem -> System.out.print(contagem + " "));
O resultado é o número de letras em cada nome:
Copie o código do código da seguinte forma:
5 4 4 4 4 5
Uma versão posterior da expressão lambda é usada para evitar operações de modificação explícitas; esse código é muito conciso. Escrever desta forma não requer mais a inicialização de uma coleção vazia e a variável lixo está oculta na implementação subjacente;
Referência do método de uso
Também podemos usar referências de método para torná-lo um pouco mais conciso. Onde uma implementação de uma interface funcional precisa ser transmitida, o compilador Java pode aceitar expressões lambda ou referências de método. Com este recurso, você pode substituir name -> name.toUpperCase() por String::toUpperCase, assim:
Copie o código do código da seguinte forma:
amigos.stream()
.map(String::toUpperCase)
.forEach(nome -> System.out.println(nome));
Quando os parâmetros são passados para o método gerado - a implementação do método abstrato da interface funcional - o Java chamará o método toUpperCase do parâmetro String. Esta referência de parâmetro está oculta aqui. Em um cenário simples como o acima, podemos usar referências de métodos para substituir expressões lambda; para obter mais informações, veja quando referências de métodos devem ser usadas na página 26.
Copie o código do código da seguinte forma:
Um amigo perguntou:
Quando você deve usar referências de método?
Ao programar em Java, geralmente usamos expressões lambda muito mais do que referências de métodos. Mas isso não significa que as referências a métodos não sejam importantes ou inúteis. Quando as expressões lambda são muito curtas, elas são uma boa alternativa para chamar diretamente métodos de instância ou métodos estáticos. Em outras palavras, se a expressão lambda apenas passar parâmetros para a chamada do método, devemos usar referências de método.
Uma expressão lambda como essa é um pouco parecida com o que Tom Smykowski disse no filme "Trabalhando com um Bug", seu trabalho é "trazer requisitos dos clientes para os engenheiros de software". Por causa disso, chamo esse padrão de referência de método de refatoração de padrão worm.
Além de ser conciso, o uso de referências de métodos pode refletir melhor o significado e a função do próprio nome do método.
Por trás do uso de referências de métodos, o compilador desempenha um papel fundamental. O objeto alvo e os parâmetros referenciados pelo método serão derivados dos parâmetros passados no método gerado. Isso permite que você use referências de método para escrever um código mais conciso do que usar expressões lambda. No entanto, se os parâmetros precisarem ser modificados antes de serem passados para o método ou se o resultado da chamada for retornado, não poderemos usar este método de escrita conveniente.
No exemplo anterior, a referência do método refere-se a um método de instância. As referências de método também podem se referir a um método estático e a um método que aceita parâmetros. Veremos exemplos disso mais tarde.
As expressões lambda podem nos ajudar a percorrer e transformar coleções. Como veremos a seguir, também nos ajuda a selecionar rapidamente um elemento de uma coleção.