domingo, 2 de março de 2014

Filtrando resultados (Predicados) - Java 8

Muitas vezes precisamos filtrar listas, listas de todos os tipos, desde números básicos, passando por Strings, até tipos complexos. Abaixo temos um exemplo simples disto, onde temos uma lista com alguns nomes e queremos filtra-la e exibi-la para o usuário (no caso, todos os nomes que tiverem a letra "u" serão exibidos):

        List<string> nomes = new ArrayList<>();

        nomes.add("João da Silva");
        nomes.add("Joaquim da Padaria");
        nomes.add("Lucas Polo");
        nomes.add("Chaves");

        List<string> nomesFiltrados = new ArrayList<>();

        System.out.println("Iniciando filtragem");

        for (String nome : nomes) {
            System.out.println("Avaliando: " + nome);
            if (nome.contains("u")) nomesFiltrados.add(nome);
        }

        System.out.println("Iniciando impressão");

        for (String nome : nomesFiltrados) {
            System.out.println(nome);
        }
A saida é:
Iniciando filtragem
Avaliando: João da Silva
Avaliando: Joaquim da Padaria
Avaliando: Lucas Polo
Avaliando: Chaves
Iniciando impressão
Joaquim da Padaria
Lucas Polo
Repare que primeiros os resultados são filtrados e depois exibidos, mas variando com o tamanho da lista, pode demorar muito tempo para filtrarmos uma lista inteira, e por isso, muitas vezes, é melhor avaliar se o elemento passa no filtro e trata-lo devidamente, ganhando assim mais velocidade. Na programação funcional, temos um meio de lidar com isso, chamado avaliação preguiçosa.

Lembrando que métodos também pode ser passados como parâmetros, por isso, podemos armazena-los e utilizar suas funções posteriormente. Métodos que realizam a avaliação de elementos, são conhecidos como predicados, que no Java é representado pela classe Predicate e contem o método filter.


Estes métodos recebem um parâmetro, avaliam se o mesmo é válido e caso sim, retorna verdadeiro. Para poder iterar sobre todos os elementos de uma coleção desta forma, precisamos converte-la em um fluxo de dados, que basicamente é feita utilizando o método stream():

        System.out.println("Iniciando filtragem");

        Stream<string> streamComFiltro = nomes.stream().filter(new Predicate<string>() {
            @Override
            public boolean test(String nome) {
                System.out.println("Avaliando: " + nome);
                return nome.contains("u");
            }
        });

        System.out.println("Iniciando impressão");

        streamComFiltro.forEach(System.out::println);
Saida:
Iniciando filtragem
Iniciando impressão
Avaliando: João da Silva
Avaliando: Joaquim da Padaria
Joaquim da Padaria
Avaliando: Lucas Polo
Lucas Polo
Avaliando: Chaves
Agora a parte mais importante, mesmo chamando o método filter() ele ainda não fez a execução do filtro, apenas quando pedimos o resultado no método foreach() é que inicia a avaliação preguiçosa, chamando conforme a necessidade.

Além disso, podemos criar métodos em tempo de execução, por isso o uso de classes anônimas não se torna mais necessário,  podemos criar então o método diretamente como parâmetro, utilizando o operador ->, desta forma, ficando desse jeito:

        System.out.println("Iniciando filtragem");

        streamComFiltro = nomes.stream().filter(nome -> {
            System.out.println("Avaliando: " + nome);
            return nome.contains("u");
        });

        System.out.println("Iniciando impressão");

        streamComFiltro.forEach(System.out::println);
Repare que desta forma, fica muito mais simples lidar com filtragens via código, deixando o código mais limpo. Mas como você pode reparar, o método stream() retorna diretamente uma Stream e depois o filter() também retorna uma Stream, então podemos resumir nosso código em uma linha (agora sem as saídas de textos informativas):

        nomes.stream()
                .filter(nome -> {return nome.contains("u");})
                .forEach(System.out::println);
Repare que conseguimos reduzir um código de 6 linhas para uma única,  deixando muito mais simples e claro. Ainda temos coisas muito mais poderosas vindo com o Java 8, como o map e reduce que permite filtrar e já processar os dados unificando eles (por exemplo, somando todos os números primos de 1 a 100). Iremos abordar essas novas funcionalidades em um próximo post.

Nenhum comentário:

Postar um comentário