O caminho do programador: Mapeando e Reduzindo - Java 8
Mapeando e Reduzindo - Java 8
As vezes precisamos processar dados em um volume maior, onde é necessário filtrar a informação, selecionar ou alterar o que queremos para depois realizar o processamento. Como exemplo, vamos imaginar uma lista de alunos, onde cada instância tem seu nome e nota. Desta lista queremos a média geral, a média dos alunos aprovados (ou seja, quem tirou nota seis ou mais) e a média dos reprovados (nota menor que seis). Para isso, vamos primeiro visualizar nossa lista:
List<Aluno>alunos = new ArrayList<>();
Aluno aluno1 = new Aluno("Cebolinha", 8.0);
Aluno aluno2 = new Aluno("Cascao", 6.5);
Aluno aluno3 = new Aluno("Monica", 9.0);
Aluno aluno4 = new Aluno("Magali", 3.0);
alunos.addAll(Arrays.asList(aluno1, aluno2, aluno3, aluno4));
Apesar da simplicidade dos nossos dados, para poder separar a nossa lista de forma a realizar esta média, precisamos iterar sobre todos os seus elementos. Vamos fazer primeiro a média mais simples, sem nenhum critério:
Double media = new Double("0");
int quantidadeDeAlunos = 0;
media = Double.sum(media, aluno.getNota());
quantidadeDeAlunos++;
System.out.println(media / quantidadeDeAlunos);
Repare no trabalho de declarar a variável e iterar sobre todos os seus elementos, ainda de maneira simples, pois ainda não temos nenhuma regra implementada, vamos agora colocar os outros dois trechos de código, implementando a média dos aprovados e reprovados:
media = new Double("0");
quantidadeDeAlunos = 0;
if (aluno.getNota() >= 6) {
if (aluno.getNota() < 6) {
Da mesma forma que meu modem toda vez que a internet cai, precisei reiniciar as variáveis de média e rescrever os for's, mas eu sei que é possível fazer em apenas uma iteração, desta forma podemos economizar tempo, vamos tentar ver a versão reduzida deste código:
Double mediaAprovados = new Double("0");
Double mediaReprovados = new Double("0");
int quantidadeDeAlunosAprovados = 0;
int quantidadeDeAlunosReprovados = 0;
if(aluno.getNota() >= 6) {
mediaAprovados = Double.sum(mediaAprovados, aluno.getNota());
quantidadeDeAlunosAprovados++;
mediaReprovados = Double.sum(mediaReprovados, aluno.getNota());
quantidadeDeAlunosReprovados++;
(Double.sum(mediaAprovados, mediaReprovados))
/ (quantidadeDeAlunosAprovados + quantidadeDeAlunosReprovados));
System.out.println(mediaAprovados / quantidadeDeAlunosAprovados);
System.out.println(mediaReprovados / quantidadeDeAlunosReprovados);
Nesta versão, conseguimos reduzir bem o nosso código, centralizando tudo em um único foreach, porém o gerenciamento manual aumentou, pois tivemos que criar novas variáveis e ainda realizar o cálculo das médias dentro das chamadas das funções de saída.
Abstraindo a função do código, chegamos a algumas conclusões que podemos traduzir melhor, a primeira coisa que fazemos é filtrar com os requisitos que queremos, ou seja, as notas, depois de filtrar as entidades Aluno, devemos selecionar qual informação vamos processar (se necessário trata-la) e depois realizamos a agregação dessa informação, somando tudo em uma variável double (por exemplo, mediaAprovados).
No artigo passado, vimos como realizar a primeira tarefa utilizando o Java 8, onde aprendemos sobre os filtros, porém ainda precisamos realizar algumas outras operações, que é selecionar a informação que queremos (mapear) e depois agrega-la (reduzir). Para nossa sorte, a nova versão do Java também possui este tratamento, onde podemos trabalhar com métodos como map() (e suas variações) e reduce(), todas elas da classe Stream. Abaixo vemos o primeiro exemplo, da média geral:
Double media = alunos.stream()
.filter(a -> a.getNota() >= 0) //Filtrando o resultado
.mapToDouble(aluno -> { return aluno.getNota(); } ) //mapeando para uma DoubleStream as notas
.average().getAsDouble(); //Pegando a média e retornando o double
Chega a ser covarde a abordagem, todas as iterações estão encapsuladas e tudo é resolvido em apenas em uma linha, com uma sintaxe bem mais limpa, além de termos fornecidos métodos que realizam operações matemáticas, como average(), facilitando toda a operação. Agora vejamos como são todas as implementações, pegando a média dos aprovados e reprovados:
Double media= alunos.stream()
.filter(a -> a.getNota() >= 0)
.mapToDouble(aluno -> { return aluno.getNota(); } )
Double mediaAprovados = alunos.stream()
.filter(a -> a.getNota() >= 6)
System.out.println(mediaAprovados);
Double mediaReprovados = alunos.stream()
.filter(a -> a.getNota() < 6)
System.out.println(mediaReprovados);
Vemos que a legibilidade do código aumenta, mas repare que os três códigos são iguais, mudando apenas o filtro, e como repetição de código é mais feio que jogar cachorro na estrada, vamos pensar numa maneira mais bonita de lidar com o código. Lembre-se que as expressões lambda, como o nosso filtro, são na verdade implementações dinâmicas da classe Filter, então podemos receber ela como parâmetro. Vamos criar um método para lidar com esse parâmetro, recebendo a lista de alunos e o filtro, retornando a sua média:
public static Double media(List<Aluno> alunos, Predicate<Aluno> filtro) {
return alunos.stream()
.filter(filtro)
Agora que temos os métodos podemos substituir o código por chamadas as funções, mas lembre-se que não é necessário criar classes anônimas, afinal as expressões lambda são convertidas automaticamente pela máquina virtual em implementações da interface:
Double media = media( alunos, a-> a.getNota() > 0 );
Double mediaAprovados = media(alunos, a -> a.getNota() >= 6 );
Double mediaReprovados = media(alunos, a -> a.getNota() < 6 );
Repare como a nossa classe ficou muito mais simples, com um código simples de se compreender e dar manutenção, podendo ampliar facilmente a função. Fica muito simples processar informações desta forma, onde podemos não apenas selecionar as informações e reduzi-la, mas podemos transforma-la antes. Poderíamos converter para String's todas as notas e concatena-las, ou mais mil e uma funções. Isso é inclusive a princípio de alguns banco de dados para processamento de grandes massas de dados, como o Hadoop, onde estas informações são utilizadas para análises de Business Inteligence e em sistemas de apoio a decisão.