Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Industrial programming >> Java

Dominando Expressões Lambda em Java: Do Básico à Stream API

Dominando Expressões Lambda em Java

Explore as expressões lambda do Java, interfaces funcionais, generics e a Stream API com exemplos claros.

O Java 8 introduziu expressões lambda para aumentar o poder expressivo da linguagem, permitindo que desenvolvedores escrevam código mais conciso e legível.

Antes de mergulhar em lambdas, é essencial entender interfaces funcionais, a espinha dorsal da implementação de lambda.


O que é uma Interface Funcional?

Uma interface funcional é uma interface Java que contém exatamente um método abstrato. Este único método define o propósito pretendido da interface.

A interface Runnable de java.lang é um exemplo clássico, expondo apenas o método run().

Exemplo 1: Definindo uma Interface Funcional

import java.lang.FunctionalInterface;

@FunctionalInterface
public interface MyInterface {
    // Único método abstrato
    double getValue();
}

Aqui, MyInterface é anotada com @FunctionalInterface, o que obriga o compilador a impor o contrato de interface funcional, prevenindo adição acidental de múltiplos métodos abstratos. A anotação é opcional mas altamente recomendada para clareza e segurança.

No Java 7, interfaces funcionais eram chamadas de Single Abstract Methods (SAMs) e eram tipicamente implementadas usando classes anônimas.

Exemplo 2: Implementando um SAM com Classe Anônima

public class FunctionInterfaceTest {
    public static void main(String[] args) {
        // Classe anônima implementando Runnable
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Acabei de implementar a Interface Funcional Runnable.");
            }
        }).start();
    }
}

Saída:

Acabei de implementar a Interface Funcional Runnable.

Embora classes anônimas reduzam boilerplate comparado a implementações tradicionais, a sintaxe permanece verbosa. Expressões lambda eliminam a necessidade de nomes de métodos explícitos ao passar interfaces funcionais como argumentos.


Introdução a Expressões Lambda

Uma expressão lambda é essencialmente um método anônimo que fornece uma implementação concisa do método abstrato de uma interface funcional. Ela não executa independentemente; em vez disso, fornece o comportamento para o método da interface.

Como Definir uma Expressão Lambda em Java

A sintaxe geral é:

(lista de parâmetros) -> corpo do lambda

O operador seta (->) separa a lista de parâmetros do corpo. Abaixo estão exemplos concretos.

Suponha que temos um método retornando Pi:

double getPiValue() { return 3.1415; }

Usando lambda:

() -> 3.1415

Sem parâmetros, o lado esquerdo da seta está vazio, e o lado direito é o corpo da expressão que retorna o valor.


Tipos de Corpos de Lambda

Corpos de lambda em Java vêm em duas formas:

1. Expression Body – contém uma única expressão.

() -> System.out.println("Lambdas são ótimas");

2. Block Body – encapsulado em chaves, permitindo múltiplas declarações e return opcional.

() -> { double pi = 3.1415; return pi; };

Nota: Expression bodies não requerem return explícito, enquanto block bodies exigem se um valor for retornado.


Exemplo 3: Expressão Lambda na Prática

Vamos escrever um programa Java que retorna Pi usando uma expressão lambda.

import java.lang.FunctionalInterface;

@FunctionalInterface
interface MyInterface {
    double getPiValue();
}

public class Main {
    public static void main(String[] args) {
        MyInterface ref = () -> 3.1415;
        System.out.println("Valor de Pi = " + ref.getPiValue());
    }
}

Saída:

Valor de Pi = 3.1415

Principais conclusões:


Expressões Lambda com Parâmetros

Como métodos, lambdas podem aceitar parâmetros. Exemplo:

(n) -> (n % 2) == 0

Este lambda verifica se o inteiro de entrada é par.

Exemplo 4: Invertendo uma String com Lambda Parametrizado

@FunctionalInterface
interface MyInterface {
    String reverse(String s);
}

public class Main {
    public static void main(String[] args) {
        MyInterface ref = (str) -> {
            String result = "";
            for (int i = str.length() - 1; i >= 0; i--) {
                result += str.charAt(i);
            }
            return result;
        };
        System.out.println("Lambda invertido = " + ref.reverse("Lambda"));
    }
}

Saída:

Lambda invertido = adbmaL

Interface Funcional Genérica

Interfaces funcionais podem ser genéricas, permitindo operar em qualquer tipo de dado. Por exemplo:

@FunctionalInterface
interface MyInterface<T> {
    T func(T t);
}

Exemplo 5: Lambdas Genéricas para Strings e Inteiros

public class Main {
    public static void main(String[] args) {
        // Inversão de String
        MyInterface<String> reverse = (str) -> {
            String result = "";
            for (int i = str.length() - 1; i >= 0; i--) {
                result += str.charAt(i);
            }
            return result;
        };
        System.out.println("Lambda invertido = " + reverse.func("Lambda"));
        // Fatorial de Inteiro
        MyInterface<Integer> factorial = (n) -> {
            int result = 1;
            for (int i = 1; i <= n; i++) {
                result *= i;
            }
            return result;
        };
        System.out.println("fatorial de 5 = " + factorial.func(5));
    }
}

Saída:

Lambda invertido = adbmaL
fatorial de 5 = 120

Expressões Lambda e a Stream API

O pacote java.util.stream, introduzido no JDK 8, capacita desenvolvedores a realizar operações de estilo funcional em coleções: filtro, map, reduce, sort e mais.

Considere uma lista de nomes de lugares, cada um prefixado por seu país. Usando streams e lambdas, podemos extrair e manipular dados eficientemente.

Exemplo 6: Processamento de Stream com Lambdas

import java.util.ArrayList;
import java.util.List;

public class StreamMain {
    static List<String> places = new ArrayList<>();

    public static List<String> getPlaces() {
        places.add("Nepal, Kathmandu");
        places.add("Nepal, Pokhara");
        places.add("India, Delhi");
        places.add("USA, New York");
        places.add("Africa, Nigeria");
        return places;
    }

    public static void main(String[] args) {
        List<String> myPlaces = getPlaces();
        System.out.println("Lugares do Nepal:");
        myPlaces.stream()
            .filter(p -> p.startsWith("Nepal"))
            .map(p -> p.toUpperCase())
            .sorted()
            .forEach(p -> System.out.println(p));
    }
}

Saída:

Lugares do Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA

Aqui, a cadeia de operações de stream — filter, map, sorted, forEach — cada uma aceita um lambda, reduzindo drasticamente boilerplate comparado a soluções pré-Java-8.

Java

  1. Dominando Interfaces em C#: Definição, Implementação e Casos Práticos
  2. Dominando Operadores Java: Tipos, Sintaxe e Exemplos Práticos
  3. Entendendo Expressões, Instruções e Blocos em Java
  4. Dominando Interfaces em Java: Conceitos, Implementação e Boas Práticas
  5. Dominando Try-With-Resources em Java: Gerenciamento Automático de Recursos Explicado
  6. Anotações em Java Explicadas: Tipos, Posicionamento e Exemplos Práticos
  7. Interface vs Classe Abstrata em Java: Como Escolher a Abstração Certa
  8. Expressões Regulares em Java: Dominando Correspondência de Padrões com java.util.regex
  9. Entendendo Interfaces em Java: Definições, Usos e Boas Práticas
  10. Java 9 Métodos Privados em Interfaces: Aumentando Encapsulamento e Reuso de Código