Skip to content

Latest commit

 

History

History
427 lines (260 loc) · 17.8 KB

File metadata and controls

427 lines (260 loc) · 17.8 KB

Expressões Lambda

Antes de falarmos sobre Expressões Lambdas, precisamos definir o conceito de Programação Funcional.


1. Programação Funcional

O paradigma da programação funcional é um estilo de programação que se baseia na avaliação de expressões e na aplicação de funções matemáticas. Ele trata a computação como a avaliação de funções e evita o uso de estados mutáveis e dados compartilhados.

Paradigma Programação é um estilo ou uma forma de se escrever programas de computador, baseado em um conjunto de princípios, conceitos e técnicas que definem como o código é estruturado, organizado e executado.

As principais características desse paradigma são:

  • Composição de função: é a criação de uma nova função a partir da combinação de outras funções.
  • Funções puras: são funções que produzem sempre o mesmo resultado se receberem os mesmos argumentos. Elas não dependem de valores mutáveis nem causam efeitos colaterais externos.
  • Imutabilidade: significa que uma vez que uma variável recebe um valor, ela vai possuir esse valor para sempre, ou quando criamos um objeto ele não pode ser modificado.

A programação funcional é inspirada nos conceitos da matemática e oferece soluções elegantes, concisas e poderosas para lidar com sistemas complexos e abstrações. O Paradigma da Programação Funcional é baseado num modelo Computacional bem antigo, chamado de Cálculo Lambda, por isso ela ganhou o nome Expressões Lambda.


2. Paradigmas de Programação - Linguagens de Alto Nível

Para uma melhor compreensão do que é o Paradigma da Programação Funcional é importante conhecer os paradigmas de programação das Linguagens de Alto Nível mais populares: Programação Imperativa e Programação Orientada a Objetos (POO).

2.1. Programação Imperativa

É o paradigma mais antigo e tradicional, que se baseia em instruções sequenciais que modificam o estado do programa. A pessoa desenvolvedora especifica como o programa deve resolver o problema, através de comandos, variáveis, atribuições, laços, condicionais, entre outros. Exemplos de linguagens imperativas são C, Pascal e Cobol.


2.2. Programação Orientada a Objetos

É o paradigma mais popular e difundido atualmente, que se baseia na abstração de entidades do mundo real em objetos que possuem atributos (dados) e métodos (comportamentos). A pessoa desenvolvedora especifica o que o programa deve fazer, através de classes, objetos, herança, polimorfismo, encapsulamento, entre outros. Exemplos de linguagens orientadas a objetos são Java, Python e C++.


2.3. E a Programação Funcional?

Diferentemente das programações Imperativa e Orientada a Objetos, a Funcional parte do princípio de que tudo são funções. Não existe uma lista de instruções ou objetos para o computador realizar, mas uma sequência de funções matemáticas que, juntas, vão resolver um problema.

Isso significa que, no paradigma funcional, você tem uma função, coloca um dado de entrada, aplica várias operações e obtém uma saída. É possível alterar as operações e, consequentemente, a saída, mas a entrada sempre permanecerá a mesma.

Além disso, no Paradigma da Programação Funcional não existem variáveis, mas constantes. Isso se traduz em códigos mais objetivos com constantes que, de forma geral, não mudam.

Algumas das vantagens de usar esse paradigma são:

  • Facilidade de testar e depurar: como as funções puras não dependem de estados externos nem causam efeitos colaterais, elas são mais fáceis de testar e depurar, pois podemos isolar e reproduzir seus comportamentos.
  • Concorrência e paralelismo: como as funções puras não compartilham dados nem modificam estados, elas podem ser executadas em paralelo sem causar conflitos ou inconsistências.
  • Expressividade e legibilidade: como as funções são compostas de forma declarativa, elas expressam melhor a intenção do código e são mais fáceis de ler e entender.

Algumas das desvantagens de usar esse paradigma são:

  • Curva de aprendizado: como a programação funcional usa conceitos e termos diferentes dos paradigmas mais comuns, como a programação orientada a objetos ou a programação imperativa, ela pode ser mais difícil de aprender e aplicar para quem está acostumado com esses estilos.
  • Desempenho: como a programação funcional evita a mutação de dados e usa estruturas de dados imutáveis, ela pode consumir mais memória e processamento do que outras abordagens, especialmente em linguagens que não são otimizadas para esse paradigma.
  • Integração: como a programação funcional é menos popular e difundida do que outros paradigmas, ela pode ter menos ferramentas, bibliotecas e recursos disponíveis para integrar com outras tecnologias e sistemas.

Algumas das linguagens de programação que suportam ou seguem o paradigma funcional são: Haskell, Clojure, Elixir, entre outras.


3. Expressões Lambda

Uma Expressão Lambda é um pequeno bloco de código que recebe parâmetros e retorna um valor. Uma expressão lambda também pode ser vista como uma função anônima. Uma função que não tem nome e não pertence a nenhuma classe. As Expressões Lambda são semelhantes aos métodos, mas não precisam de um nome e podem ser implementadas diretamente no corpo de um método. Através das Expressões Lambda o Java adicionou novas Classes, que implementam os conceitos da Programação Funcional, tornando a Linguagem Java mais flexível, servindo como um complemento para a construção de códigos mais limpos e enxutos.

Sintaxe:

lista de parâmetros -> corpo da função

Uma Expressão Lambda consiste em três componentes, um conjunto de parâmetros, um operador Lambda e um corpo de função. Aqui estão os três componentes:

  • Lista de Parâmetros: Aqui vem o argumento que pode ser vazio ou não vazio também.
  • Expressão Lambda: Expressão Lambda ou seta (->) é usada para separar a lista de parâmetros e o corpo da função.
  • Corpo da função: contém a instrução de função para Expressão Lambda.

Os parâmetros de entrada estão no lado esquerdo do Operador Lambda e o corpo da função no lado direito do Operador Lambda. Essa sintaxe do Lambda Expression reduz o volume do código, que é de cinco linhas de código em uma linha.


2.1. Características da Expressão Lambda

As principais características das Expressões Lambda são:

  1. Declaração de tipo: a declaração de tipo é opcional. Se você não declarar o tipo de parâmetro, o compilador Java consegue inferir (descobrir) o valor do parâmetro.

Exemplo 01: Expressão Lambda sem declaração de tipo:

(5,4) -> corpo da função

  1. Parênteses ao redor do parâmetro: O parêntese ao redor do parâmetro é opcional. Você pode colocar parênteses se quiser, caso contrário, deixe como está. Se houver mais de um parâmetro em uma Expressão Lambda, os parênteses são obrigatórios.

Exemplo 02: Expressão Lambda com 1 Parâmetro:

5 -> corpo da função

Exemplo 03: Expressão Lambda com 2 Parâmetros:

(5, 4) -> corpo da função

  1. Chaves: As chaves ao redor do corpo da função também são opcionais se houver apenas uma instrução. Para várias declarações, as chaves em torno do corpo da função são necessárias.

Exemplo 04: Expressão Lambda com várias instruções:

n -> {
	instrução 01
	instrução 02
	instrução 03
	};

  1. Instrução de retorno: A instrução de retorno (return) também é opcional em Expressões Lambda. O compilador Java retorna automaticamente o valor se o corpo tiver uma única expressão. Se o corpo da função retornar um valor, você deve colocá-lo entre chaves.

Exemplo 05: Expressão Lambda com retorno:

n -> {
	int retorno = n * n;
	return retorno;
}

Exemplo 06 - Expressões Lambda e Collections:

Nesta primeira versão do código, não foram utilizadas as Expressões Lambdas:

package lambda_collections_01;

import java.util.Arrays;
import java.util.List;

public class Lambda {
	public static void main(String[] args) {

		List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

		System.out.println("Exibir os itens\n");
		
		// Exibir os itens da Lista sem Expressão Lambda
		for(Integer n : numeros) {
			System.out.println(n);
		}

		System.out.println("\nExibir os itens somados com eles mesmos\n");
		
		// Exibir os itens da Lista dobrados sem Expressão Lambda
		for(Integer n : numeros) {
			System.out.println(n + n);
		}

		System.out.println("\nExibir os itens pares da lista\n");
		
		// Exibir apenas os elementos pares da Lista sem Expressão Lambda
		for(Integer n : numeros) {
			if(n%2 == 0)
			System.out.println(n);
		}
	}
}


Na segunda versão do código, foram utilizadas as Expressões Lambdas:

package lambda_collections_02;

import java.util.Arrays;
import java.util.List;

public class Lambda {
	public static void main(String[] args) {

		List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

		System.out.println("Exibir os itens\n");
		
		// Exibir os itens da Lista com Expressão Lambda
		numeros.forEach(n -> System.out.println(n));

		System.out.println("\nExibir os itens somados com eles mesmos\n");
		
		// Exibir os itens da Lista dobrados com Expressão Lambda
		numeros.forEach(n -> System.out.println(n + n));

		System.out.println("\nExibir os itens pares da lista\n");
		
		// Exibir apenas os elementos pares da Lista com Expressão Lambda
		numeros.forEach(n -> {
			if (n % 2 == 0)
				System.out.println(n);
		});
	}
}

Observe que ao comparar a primeira versão sem Expressões Lambdas, com a segunda versão com Expressões Lambdas, o código da segunda versão fica muito menos verboso, mais direto e limpo. Observe que as Expressões Lambda foram inseridas dentro do Método forEach(), da Classe Collections, porque as Expressões Lambda dependem de uma Interface Funcional para serem executadas.

O Método forEach(), que é utilizado para iterar sobre uma coleção e executar uma determinada ação em cada elemento, utiliza uma classe que implementa a interface Consumer, que é passada para o Método forEach() como um argumento do Método. Como a interface Consumer é uma interface funcional, a ação que será executada sobre a Collection, será enviada na forma de uma Expressão Lambda.

Abaixo, vemos o resultado da execução de ambos os códigos.

Resultado do Algoritmo:

Exibir os itens

1
2
3
4
5
6
7
8
9

Exibir os itens somados com eles mesmos

2
4
6
8
10
12
14
16
18

Exibir os itens pares da lista

2
4
6
8



4. Interfaces Funcionais

Interfaces Funcionais são interfaces que têm um único Método Abstrato a ser implementado. Isso significa que toda Interface criada que respeite esta premissa, torna-se automaticamente uma Interface Funcional.

Um ponto importante que deve ser destacado é que o compilador Java reconhece automaticamente essas Interfaces e a partir da Expressão Lambda enviada, ele sabe exatamente qual Operação será executada pelo Método da Interface.

Para identificar que uma Interface é Funcional utilizamos a anotação @FunctionalInterface. O compilador exibe um alerta 'Annotation @FunctionalInterface inesperado' se a Interface contiver mais de um método abstrato. No entanto, não há obrigação de utilizar esta anotação.


Exemplo 07 - Expressões Lambdas e Interface Funcional:

Neste exemplo, vamos criar uma Calculadora utilizando Interfaces Funcionais e Expressões Lambdas.

Vamos começar implementando a Interface Funcional OperacaoMatematica, que será responsável por executar todas as Operações matemáticas da nossa Calculadora:

Interface OperacaoMatematica

package lambda;

@FunctionalInterface
public interface OperacaoMatematica {

	public int executar(int a, int b);

}

Dentro da nossa Interface foi assinado o Método Abstrato executar, com 2 parâmetros inteiros.

Na sequência, vamos implementar a Classe Calculadora, onde implementaremos a Calculadora propriamente dita:

Classe Calculadora

package lambda;

import java.util.Scanner;

public class TestaCalculadora {

	public static int calcular(OperacaoMatematica om, int num1, int num2) {
        return om.executar(num1, num2);
    }
	
	public static void main(String[] args) {

		Scanner leia = new Scanner(System.in);
		
		int n1, n2 = 0;
		
		System.out.println("****************************************************");
		System.out.println("              Calculadora com Lambda                ");
		System.out.println("****************************************************");
		System.out.println("Digite o primeiro numero: ");
		n1 = leia.nextInt();
		System.out.println("Digite o segundo numero: ");
		n2 = leia.nextInt();
		
		System.out.println("Soma = " + calcular((a,b) -> a + b, n1, n2));
        System.out.println("Subtração = " + calcular((a,b) -> a - b, n1, n2));
        System.out.println("Multiplicação = " + calcular((a,b) -> a * b, n1, n2));
        System.out.println("Divisão = " + calcular((a,b) -> a / b, n1, n2));
		
        leia.close();
        
	}
		
}

Observe que foi criado o Método Calcular, que receberá 3 parâmetros: Um Objeto da Interface OperacaoMatematica, que receberá a Expressão Lambda contendo a Operação Matemática que o Método executar() irá calcular e 2 parâmetros inteiros, que serão a lista de parâmetros da Expressão Lambda.

Depois da entrada de dados, vamos observar a linha da operação de soma:

System.out.println("Soma = " + calcular((a,b) -> a + b, n1, n2));

Observe que na chamada do Método Calcular, foi passado no primeiro parâmetro a Expressão Lambda responsável por efetuar a soma, que será processada pela Interface Funcional:

//Expressão Lambda
(a,b) -> a + b

Na sequência foram inseridos os 2 números inteiros recebidos via teclado, que substituirão os parâmetros a e b na Expressão Lambda, efetuando a soma dos dois números.

As demais Operações seguem a mesma linha de raciocínio.

Resultado do Algoritmo:

****************************************************
              Calculadora com Lambda                
****************************************************
Digite o primeiro numero: 
10
Digite o segundo numero: 
5
Soma = 15
Subtração = 5
Multiplicação = 50
Divisão = 2



🔑Pontos chave:

  • Paradigma imperativo: é o paradigma mais antigo e tradicional, que se baseia em instruções sequenciais que modificam o estado do programa.
  • Paradigma orientado a objetos: é o paradigma mais popular e difundido atualmente, que se baseia na abstração de entidades do mundo real em objetos que possuem atributos (dados) e métodos (comportamentos).
  • Paradigma funcional: é o paradigma inspirado na matemática, que se baseia na avaliação de expressões e na aplicação de funções.
  • Uma Expressão Lambda é um pequeno bloco de código que recebe parâmetros e retorna um valor. Uma expressão lambda também pode ser vista como uma função anônima. Uma função que não tem nome e não pertence a nenhuma classe.
  • Interfaces Funcionais são interfaces que têm um único Método Abstrato a ser implementado. Isso significa que toda Interface criada que respeite esta premissa, torna-se automaticamente uma Interface Funcional.