Aprenda Java com Lambda

  • Página Inicial
  • Contato!
  • Tudo sobre Java com Lambda Parte 1!
  • Tudo sobre Java com Lambda Parte 2!
  • Tudo sobre Java com Lambda Parte 1

    Expressão Lambdas

    No Java 8 foi adicionado as expressões Lambda. As expressões Lambda são métodos anônimos, só que esses métodos não são executados em si próprios. Elas também são chamadas de métodos anônimos. Basicamente são funções sem nome. Dessa forma, facilmente implementar mensagens de alto nível de abstração, que podem receber ou enviar blocos de código que se comportam de forma parecida com funções de ordem superior, encontrada de forma recorrente no paradigma funcional (operação que recebe outra operação, um comportamento por parâmetro, assim podendo executar internamente esse comportamento).

    As expressões Lambda se fala muito do paradigma funcional, você pode entender o paradigma funcional como uma forma de programar onde existe um conjunto de funções que podem ser verificadas para obter determinado resultado. Isso é um conceito da matemática que foi aplicado na programação, porém, a programação funcional é um termo que representa coisas diferentes para pessoas diferentes. Podemos perceberela, mas explicar não é muito fácil.

    As expressões Lambdas implementa os métodos que estão definidos em uma interface funcional. Utilizaremos muito para substituir a implementação da classe anônima, logo, com o advento das expressões Lambdas, conseguimos pegar as classes anônimas de forma mais elegante e que melhora a visibilidade do código.

    As interfaces funcionais são aquelas que contém somente um método abstrato, e assim, esse método especifica a finalidade dessa interface. Normalmente representarão apenas uma única ação, e acabam fornecendo um tipo alvo para as expressões Lambdas e a referências de métodos. Cada interface funcional sempre terá um método abstrato (chamado de método funcional da interface funcional), aonde o parâmetro da expressão Lambda e os tipos de retorno serão combinados e adaptados. Como veremos, as interfaces funcionais geralmente apresentarão a anotação @FunctionalInterface, mas ela não é pré-requisito pro JDK reconhecer as interfaces funcionais.

    Essas são as interfaces funcionais usadas:

    Expressão Lambdas na Prática

    Vamos criar um projeto novo no qual criaremos uma implementação da interface anônima Runnable (classe interna do Java):

    
    package expressaolambda;
    
    public class ExpressaoLambda {
        public static void main(String[] args) {
            System.out.println("=== Início do Teste ===");
            
            // Implementação da interface anônima Runnable sem Lambda:
            
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
                    System.out.println("Estudando a Expressão Lambda (Teste sem Lambda)!");
                }           
            }; // Não esqueça do ponto e vírgula aqui.
        }
    }
    
    

    Para entender o que acontece, o Java tem uma interface padrão chamada Runnable, da qual é criada uma instância, essa interface possui apenas um método void chamado run (que é abstrato na interface), que deve ser implementado na instância criada.

    Para transformar em Lambda, usamos apenas os parênteses e a indicação da seta, colocando tudo numa linha só veja o exemplo abaixo, sem e com Lambda:

    
    package expressaolambda;
    
    public class ExpressaoLambda {
        public static void main(String[] args) {
            System.out.println("=== Início do Teste ===");
            
            // Implementação da interface anônima Runnable sem Lambda:
            
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
                    System.out.println("Estudando a Expressão Lambda (Teste sem Lambda)!");
                }           
            };
            
            // Implementação com Lambda:
            
            Runnable r2 = () -> System.out.println("Estudando a Expressão Lambda (Teste com Lambda)!");
        }
    }
    
    

    Dessa forma, podemos facilitar a leitura e a criação de métodos, por diminuir a quantidade de linhas no programa. Os parênteses indicam a implementação do único método da interface padrão Runnable, run().

    Para invocar os métodos, faça da mesma forma que um objeto qualquer, dessa forma:

    
    r1.run();
    r2.run();
    
    

    Para entendermos a expressão Lambda, vamos ver as partes dela e o que cada uma faz:

    
    Runnable r2; // Declaração da variável do objeto.
    
    r2 = () // Os parênteses indica uma lista de argumentos (vazia ou não).
    -> // Seta que demarca que estamos trabalhando com Lambda
    System.out.println("Estudando Lambda!"); // Tudo que vier aqui é a parte programada, no caso, que implementa o método run da interface.
    
    

    PS: A variável já pode ser declarada e inicializada normalmente. Os parênteses são obrigatórios mesmo sem nenhum argumento. A expressão pode estar em uma só linha ou não.

    Sintaxe do Lambda

    Uma expressão Lambda descreve uma função anônima, por isso trabalhamos de forma muito semelhante de quando trabalhamos com uma função, ou seja, numa função Lambda também temos uma lista de parâmetros e também um bloco de instrução.

    Veja abaixo um exemplo simplificado de uma expressão Lambda:

    
    () -> x + y;
    
    

    No caso acima, os parênteses temos a lista de argumentos, sem nome. A seta que indica o uso de expressão Lambda, e tudo que vier depois dela é o bloco de instrução, que não é definido com o uso de chaves, e quando tem uma só instrução não precisa de indicação de retorno (mesmo querendo retornar algo). Além disso, não possuí nome e nem precisa da declaração throw para tratamento.

    As variáveis dos parâmetros podem ter tipos declarados, mas não é necessário, o próprio Java identificará isso, por exemplo:

    
    (int x) -> x + 10;
    (int x, int y) -> x + y;
    
    

    Podemos declarar utilizando chaves também, mas eles são dispensáveis em blocos de uma só linha, pois o JDK já entenderá que é um bloco:

    
    (int x, int y) -> {x + y};
    
    

    Interfaces Funcionais

    No Java 8, todas as interfaces com um só método declarado, são consideradas uma interface funcional (lembrando que os métodos declarados dentro de interfaces são abstratas por default). Até o Java 7 tinha que declarar os métodos como abstratos, no 8 foi dispensado isso.

    No caso do Java, as interfaces funcionais permitem que trabalhemos com expressões lambdas que necessitem retornar um valor e/ou que recebam algum parâmetro (já que a padrão da Runnable é void e sem parâmetros).

    Crie um programa com classe principal, e crie uma interface dentro na classe:

    
    package interfacefuncional;
    
    public class InterfaceFuncional {
        // Interface funcional:
        interface Num {
            double getValue(); // Função que retornará um double
        }
                
        public static void main(String[] args) {
            
        }
    }
    
    

    PS: É comum ter a indicação @FunctionalInterface em interfaces funcionais, mas não é obrigatório, já que o JDK identifica as interfaces funcionais automaticamente.

    Na interface acima, o método getValue() já é abstrato por default, e ela é uma interface funcional por ter só um método abstrato.

    Como estamos trabalhando com expressões Lambda, não temos como executar uma expressão Lambda em cima dela mesma, por isso, quando quisermos trabalhar com retorno de valores em Lambda, temos que fazer uso de uma interface (o Runnable mesmo é uma interface padrão do Java, mas não permite retorno).

    Crie uma variável no método principal, e uma expressão Lambda, assim:

    
    public static void main(String[] args) {
        Num n;
        n = () -> { // Implementação da função getValue() criada
            return 333.11;
        };
    
        System.out.println(n.getValue());
    }
    
    

    Ou de forma mais simples:

    
    public static void main(String[] args) {
        Num n;
        n = () -> 333.11; // Implementação da função getValue() criada, não precisa indicar return nesse caso
    
        System.out.println(n.getValue());
    }
    
    

    No exemplo acima, é criado uma instância da interface Num, que foi implementada pela expressão Lambda, e quando o método é invocado pelo System.out.prinln, a expressão Lambda é executada e o valor contido nela é retornado.

    Dessa forma, conseguimos transformar um segmento de código em um objeto, já que quando executamos, o corpo da expressão ainda não foi executado, ele só é executado quando invocamos o método definido da interface.

    Lembrando que as expressões Lambda devem ter o conteúdo de um tipo compatível com o retorno, no mesmo retorno acima, se colocarmos uma string, dará erro. O mesmo vale para passar atributos por parâmetros (claro, podemos passar parâmetros de tipos diferente do retorno).

    Expressões Lambda 2

    Continuando o exemplo anterior, vamos criar um novo objeto com Lambda dessa forma:

    
    package expressoeslambda;
    
    public class ExpressoesLambda {
        @FunctionalInterface
        interface Num {
            double getValue();
        }
                
        public static void main(String[] args) {
            Num n1 = () -> 333.11;
            Num n2 = () -> Math.random() * 100;
            
            System.out.println(n1.getValue());
            System.out.println(n2.getValue());
            System.out.println(n2.getValue());
        }
    }
    
    

    No caso acima, o n1 seria uma expressão como uma constante, já a n2 é uma variante, se charmarmos o mesmo método mais de uma vez, gerará números diferentes.

    Crie uma interface funcional dentro da classe, dessa forma:

    
    @FunctionalInterface
    interface ValorNumeric {
        boolean verif(int n);
    }
    
    

    E crie um objeto com expressão Lambda para verificarmos se o número é par:

    
    ValorNumeric isPar = (int num) -> num % 2 == 0;
    
    

    E depois invoque os métodos assim:

    
    package expressoeslambda;
    
    public class ExpressoesLambda {
        @FunctionalInterface
        interface Num {
            double getValue();
        }
        
        @FunctionalInterface
        interface ValorNumeric {
            boolean verif(int n);
        }
                
        public static void main(String[] args) {
            Num n1 = () -> 333.11;
            Num n2 = () -> Math.random() * 100;
            
            System.out.println(n1.getValue());
            System.out.println(n2.getValue());
            System.out.println(n2.getValue());
            
            ValorNumeric isPar = (int num) -> num % 2 == 0;
            
            System.out.println(isPar.verif(89)); // Retorna false
            System.out.println(isPar.verif(90)); // Retorna true
        }
    }
    
    

    Expressões Lambda 3

    Continuando a aula anterior, criaremos uma nova interface com dois parâmetros, dessa forma:

    
    @FunctionalInterface
    interface Valores {
        boolean divisao(int n1, int n2);
    }
    
    

    Crie também o objeto assim:

    
    Valores isDiv = (x, y) -> x % y == 0;
    
    

    PS: Note que acima nem precisamos declarar os tipos primitivos de x e y, o próprio compilador identificará os tipos. Mas se identificar o tipo de um, terá obrigatoriamente que definir o tipo de todos os outros (ou define tudo ou define nada)

    E faça a exibição dos métodos assim:

    
    System.out.println(isDiv.divisao(10, 2));
    System.out.println(isDiv.divisao(10, 3));
    
    

    Esse é o código completo:

    
    package expressoeslambda;
    
    public class ExpressoesLambda {
        @FunctionalInterface
        interface Num {
            double getValue();
        }
        
        @FunctionalInterface
        interface ValorNumeric {
            boolean verif(int n);
        }
        
        @FunctionalInterface
        interface Valores {
            boolean divisao(int n1, int n2);
        }
                
        public static void main(String[] args) {
            Num n1 = () -> 333.11;
            Num n2 = () -> Math.random() * 100;
            
            System.out.println(n1.getValue());
            System.out.println(n2.getValue());
            System.out.println(n2.getValue());
            
            ValorNumeric isPar = (int num) -> num % 2 == 0;
            
            System.out.println(isPar.verif(89)); // Retorna false
            System.out.println(isPar.verif(90)); // Retorna true
            
            Valores isDiv = (x, y) -> x % y == 0;
            System.out.println(isDiv.divisao(10, 2)); // Retorna true
            System.out.println(isDiv.divisao(10, 3)); // Retorna false
        }
    }
    
    

    PS: Também podemos fazer assim, nesse caso devemos colocar obrigatoriamente a expressão return:

    
    Valores expressao = (int x, int y) -> {
        int w = x + y;
        return w > 1000;
    };
    
    System.out.println(expressao.divisao(2000, 2));