Capítulo 2: Uso de Coleções
Freqüentemente usamos várias coleções, números, strings e objetos. Eles estão por toda parte, e mesmo que o código que opera a coleção possa ser ligeiramente otimizado, isso tornará o código muito mais claro. Neste capítulo, exploramos como usar expressões lambda para manipular coleções. Nós o usamos para percorrer coleções, converter coleções em novas coleções, remover elementos de coleções e mesclar coleções.
Percorra a lista
Percorrer uma lista é a operação de conjunto mais básica e suas operações sofreram algumas alterações ao longo dos anos. Utilizamos um pequeno exemplo de passagem de nomes, apresentando-o desde a versão mais antiga até a versão mais elegante da atualidade.
Podemos criar facilmente uma lista imutável de nomes com o seguinte código:
Copie o código do código da seguinte forma:
lista final<String> amigos =
Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
System.out.println(amigos.get(i));
}
O seguinte é o método mais comum de percorrer uma lista e imprimi-la, embora também seja o mais geral:
Copie o código do código da seguinte forma:
for(int i = 0; i < amigos.size(); i++) {
System.out.println(amigos.get(i));
}
Eu chamo essa forma de escrever masoquista – é prolixa e propensa a erros. Temos que parar e pensar sobre isso: "É i< ou i<=?" Isso só faz sentido quando precisamos operar em um elemento específico, mas mesmo assim, ainda podemos usar expressões funcionais que aderem ao princípio de estilo de imutabilidade, que discutiremos em breve.
Java também fornece uma estrutura relativamente avançada.
Copie o código do código da seguinte forma:
coleções/fpij/Iteration.java
for(String nome: amigos) {
System.out.println(nome);
}
Nos bastidores, a iteração dessa maneira é implementada usando a interface Iterator, chamando seus métodos hasNext e next. Ambos os métodos são iteradores externos e combinam como fazer com o que você deseja fazer. Controlamos explicitamente a iteração, informando onde começar e onde terminar; a segunda versão faz isso nos bastidores por meio do método Iterator. Na operação explícita, você também pode usar instruções break e continue para controlar a iteração. A segunda versão tem algumas coisas faltando na primeira. Esta abordagem é melhor que a primeira se não pretendemos modificar um elemento da coleção. No entanto, ambos os métodos são imperativos e devem ser abandonados no Java atual. Existem vários motivos para mudar para um estilo funcional:
1. O loop for em si é serial e difícil de paralelizar.
2. Esse loop não é polimórfico; o que você obtém é o que você pede. Passamos a coleção diretamente para o loop for, em vez de chamar um método (que suporta polimorfismo) na coleção para realizar uma operação específica.
3. Do ponto de vista do design, o código escrito dessa maneira viola o princípio "Diga, não pergunte". Solicitamos que uma iteração seja realizada em vez de deixar a iteração para a biblioteca subjacente.
É hora de mudar da antiga programação imperativa para a programação funcional mais elegante de iteradores internos. Depois de usar iteradores internos, deixamos muitas operações específicas para a biblioteca de métodos subjacente para execução, para que você possa se concentrar mais em requisitos de negócios específicos. A função subjacente será responsável pela iteração. Primeiro usamos um iterador interno para enumerar a lista de nomes.
A interface Iterable foi aprimorada no JDK8. Ela possui um nome especial chamado forEach, que recebe um parâmetro do tipo Comsumer. Como o nome diz, uma instância Consumer consome o objeto passado a ela por meio de seu método de aceitação. Usamos a familiar sintaxe de classe interna anônima para usar o método forEach:
Copie o código do código da seguinte forma:
friends.forEach(new Consumer<String>() { public void aceitar(nome da string final) {
System.out.println(nome);
});
Chamamos o método forEach na coleção de amigos, passando-lhe uma implementação anônima de Consumer. Este método forEach chama o método accept do Consumer passado para cada elemento da coleção, permitindo processar esse elemento. Neste exemplo apenas imprimimos seu valor, que é o nome. Vamos dar uma olhada no resultado desta versão, que é igual às duas anteriores:
Copie o código do código da seguinte forma:
Brian
Nate
Neil
Raju
Sara
Scott
Mudamos apenas uma coisa: abandonamos o loop for obsoleto e usamos um novo iterador interno. A vantagem é que não precisamos especificar como iterar a coleção e podemos nos concentrar mais em como processar cada elemento. A desvantagem é que o código parece mais detalhado – o que quase mata a alegria do novo estilo de codificação. Felizmente, isso é fácil de mudar e é aí que o poder das expressões lambda e dos novos compiladores entra em ação. Vamos fazer mais uma modificação e substituir a classe interna anônima por uma expressão lambda.
Copie o código do código da seguinte forma:
friends.forEach((nome da string final) -> System.out.println(nome));
Parece muito melhor assim. Há menos código, mas vamos primeiro ver o que isso significa. O método forEach é uma função de ordem superior que recebe uma expressão lambda ou bloco de código para operar nos elementos da lista. A cada chamada, os elementos da coleção serão vinculados à variável name. A biblioteca subjacente hospeda a atividade de invocação de expressão lambda. Pode decidir atrasar a execução de expressões e, se apropriado, realizar cálculos paralelos. A saída desta versão também é a mesma da anterior.
Copie o código do código da seguinte forma:
Brian
Nate
Neil
Raju
Sara
Scott
A versão do iterador interno é mais concisa. Além disso, ao utilizá-lo, podemos focar mais no processamento de cada elemento ao invés de percorrê-lo - isso é declarativo.
No entanto, esta versão tem falhas. Uma vez que o método forEach começa a ser executado, ao contrário das outras duas versões, não podemos sair desta iteração. (É claro que existem outras maneiras de fazer isso). Portanto, essa forma de escrita é mais comumente utilizada quando cada elemento da coleção precisa ser processado. Posteriormente apresentaremos algumas outras funções que nos permitem controlar o processo do loop.
A sintaxe padrão para expressões lambda é colocar os parâmetros dentro de (), fornecer informações de tipo e usar vírgulas para separar os parâmetros. Para nos libertar, o compilador Java também pode realizar automaticamente a dedução de tipos. Claro que é mais conveniente não escrever tipos. Há menos trabalho e o mundo fica mais silencioso. A seguir está a versão anterior após a remoção do tipo de parâmetro:
Copie o código do código da seguinte forma:
friends.forEach((nome) -> System.out.println(nome));
Neste exemplo, o compilador Java sabe que o tipo de nome é String por meio de análise de contexto. Ele analisa a assinatura do método chamado forEach e depois analisa a interface funcional nos parâmetros. Em seguida, analisará o método abstrato nesta interface e verificará o número e tipo de parâmetros. Mesmo que esta expressão lambda receba vários parâmetros, ainda podemos realizar a dedução de tipo, mas neste caso todos os parâmetros não podem ter tipos de parâmetro em expressões lambda, os tipos de parâmetro devem ser escritos ou, se forem escritos, devem ser escritos; na íntegra.
O compilador Java trata especialmente as expressões lambda com um único parâmetro: se você deseja realizar inferência de tipo, os parênteses ao redor do parâmetro podem ser omitidos.
Copie o código do código da seguinte forma:
amigos.forEach(nome -> System.out.println(nome));
Há uma pequena ressalva aqui: os parâmetros usados para inferência de tipo não são do tipo final. No exemplo anterior de declaração explícita de um tipo, também marcamos o parâmetro como final. Isso evita que você altere o valor do parâmetro na expressão lambda. De modo geral, é um mau hábito modificar o valor de um parâmetro, o que pode facilmente causar BUG, por isso é um bom hábito marcá-lo como final. Infelizmente, se quisermos usar inferência de tipos, teremos que seguir as regras e não modificar os parâmetros, porque o compilador não nos protege mais.
Foi preciso muito esforço para chegar a esse ponto, mas agora a quantidade de código é realmente um pouco menor. Mas isso ainda não é o mais simples. Vamos tentar esta última versão minimalista.
Copie o código do código da seguinte forma:
amigos.forEach(System.out::println);
No código acima usamos uma referência de método. Podemos substituir diretamente todo o código pelo nome do método. Exploraremos isso em profundidade na próxima seção, mas por enquanto vamos relembrar uma famosa citação de Antoine de Saint-Exupéry: A perfeição não é o que pode ser adicionado, mas o que não pode mais ser retirado.
As expressões lambda nos permitem percorrer coleções de forma concisa e clara. Na próxima seção, falaremos sobre como isso nos permite escrever um código tão conciso ao realizar operações de exclusão e conversões de coleção.