Expresiones Lambda en Java: conceptos y ejemplos prácticos

Introducción a las expresiones Lambda

Una expresión Lambda representa una función anónima que puede asignarse a una variable o pasarse como argumento. En Java, su uso se apoya en interfaces funcionales, es decir, aquellas que declaran un único método abstracto. Este enfoque permite escribir código más conciso y favorece el uso de la programación funcional dentro del lenguaje orientado a objetos.

Ventajas e inconvenientes

Ventajas:

  1. Reduce la verbosidad del código y acelera el desarrollo.
  2. Facilita el procesamiento paralelo con parallelStream.
  3. Optimiza operaciones como computeIfAbsent en mapas.
  4. Evita la generación de clases extra asociadas a clases anónimas.

Inconvenientes:

  1. Puede disminuir la legibilidad si se abusa de ellas.
  2. Depurar resulta más complicado.
  3. Restringen la modificación de variables externas dentro de ciertos contextos.
  4. En cálculos secuenciales pequeños, pueden ser menos eficientes que un bucle tradicional.

Sintaxis básica

(parámetros) -> { cuerpo }

// Si hay un único parámetro, se pueden omitir los paréntesis.
// Si el cuerpo es una sola instrucción, se pueden omitir las llaves y el return.

Procesamiento paralelo con streams

Java permite convertir un stream convencional en un stream parlaelo mediante parallelStream() o parallel(). Esto divide los datos en fragmentos que se procesan simultáneamenet en diferentes núcleos.

import java.util.List;

public class ProcesoParalelo {
    public static void main(String[] args) {
        List<integer> numeros = List.of(1, 2, 3, 4, 5, 6);

        numeros.parallelStream().forEach(n -> System.out.println(n));
        numeros.parallelStream().filter(n -> n > 2).forEach(System.out::println);
        numeros.parallelStream().map(n -> n * 2).forEach(System.out::println);

        Integer total = numeros.parallelStream().reduce(0, (a, b) -> a + b);
        numeros.parallelStream().sorted().forEach(System.out::println);
    }
}
</integer>

No siempre el paralelismo mejora el rendimiento; solo es recomendable cuando el procesamiento es intensivo en CPU.

Ejemplos prácticos

Recorrido con forEach

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

public class RecorridoEjemplo {
    public static void main(String[] args) {
        List<integer> valores = new ArrayList<>();
        Collections.addAll(valores, 1, 2, 3, 4, 5, 6, 7, 8, 9);

        valores.forEach(v -> System.out.println(v));
        valores.forEach(System.out::println);

        valores.forEach(v -> {
            if (v % 2 == 0) {
                System.out.println(v);
            }
        });
    }
}
</integer>

Reemplazo de clases aónimas

public class EjemploFuncional {
    interface Conversor {
        int convertir(String texto);
    }

    public static void main(String[] args) {
        Conversor conClaseAnonima = new Conversor() {
            @Override
            public int convertir(String texto) {
                return Integer.parseInt(texto);
            }
        };

        Conversor conLambda = texto -> Integer.parseInt(texto);

        System.out.println(conClaseAnonima.convertir("100"));
        System.out.println(conLambda.convertir("200"));
    }
}

Manejo de Optional

import java.util.Optional;

public class EjemploOptional {
    public static void main(String[] args) {
        Optional<string> mensaje = Optional.ofNullable("Hola Java");
        mensaje.ifPresent(texto -> System.out.println(texto));

        String resultado = mensaje.orElseGet(() -> "Valor por defecto");
        System.out.println(resultado);
    }
}
</string>

Suma paralela

import java.util.List;

public class CalculoParalelo {
    public static void main(String[] args) {
        List<string> cadenas = List.of("10", "20", "30", "40");
        System.out.println(sumar(cadenas));
    }

    public static int sumar(List<string> cadenas) {
        return cadenas.parallelStream()
                      .mapToInt(Integer::parseInt)
                      .sum();
    }
}
</string></string>

Ordenación de colecciones

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

class Empleado {
    private String nombre;
    private int edad;

    public Empleado(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public String getNombre() { return nombre; }
    public int getEdad() { return edad; }
}

public class OrdenacionEjemplo {
    public static void main(String[] args) {
        List<empleado> equipo = new ArrayList<>();
        equipo.add(new Empleado("Ana", 30));
        equipo.add(new Empleado("Luis", 25));
        equipo.add(new Empleado("Marta", 35));

        equipo.sort((e1, e2) -> e1.getEdad() - e2.getEdad());
        equipo.sort(Comparator.comparingInt(Empleado::getEdad));
        equipo.sort(Comparator.comparing(Empleado::getNombre).reversed());

        List<empleado> ordenados = equipo.stream()
            .sorted(Comparator.comparing(Empleado::getEdad))
            .toList();
    }
}
</empleado></empleado>

TreeSet con Comparator

import java.util.TreeSet;

class Persona {
    String nombre;
    int edad;

    public Persona(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    @Override
    public String toString() {
        return nombre + " (" + edad + ")";
    }
}

public class EjemploTreeSet {
    public static void main(String[] args) {
        TreeSet<persona> conjunto = new TreeSet<>((p1, p2) -> Integer.compare(p2.edad, p1.edad));
        conjunto.add(new Persona("Sofía", 28));
        conjunto.add(new Persona("Pedro", 35));
        conjunto.add(new Persona("Elena", 22));

        System.out.println(conjunto);
    }
}
</persona>

Eliminación condicional con removeIf

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

public class EliminacionEjemplo {
    public static void main(String[] args) {
        List<persona> personas = new ArrayList<>();
        personas.add(new Persona("Carlos", 15));
        personas.add(new Persona("Diana", 22));
        personas.add(new Persona("Bruno", 12));

        personas.removeIf(p -> p.edad >= 18);
        System.out.println(personas);
    }
}
</persona>

Agrupación y partición

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class AgrupacionEjemplo {
    public static void main(String[] args) {
        List<string> palabras = List.of("java", "lambda", "stream", "api");

        Map<integer list="">> porLongitud = palabras.stream()
            .collect(Collectors.groupingBy(String::length));

        Map<boolean list="">> porTamano = palabras.stream()
            .collect(Collectors.partitioningBy(p -> p.length() > 3));

        System.out.println(porLongitud);
        System.out.println(porTamano);
    }
}
</boolean></integer></string>

Creación de hilos

public class HiloLambda {
    public static void main(String[] args) {
        Thread hilo = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        });
        hilo.start();
    }
}

Uso del API Stream

filter

List<string> nombres = List.of("Ana", "Luis", "María", "Juan");

List<string> sinLuis = nombres.stream()
    .filter(n -> !"Luis".equals(n))
    .toList();

String encontrado = nombres.stream()
    .filter(n -> "Luis".equals(n))
    .findAny()
    .orElse("No existe");
</string></string>

map

List<string> minusculas = List.of("java", "lambda", "stream");

List<string> mayusculas = minusculas.stream()
    .map(String::toUpperCase)
    .toList();

List<integer> enteros = List.of("1", "2", "3").stream()
    .map(Integer::valueOf)
    .toList();

List<integer> cuadrados = List.of(1, 2, 3, 4).stream()
    .map(n -> n * n)
    .toList();
</integer></integer></string></string>

sorted, max, min y distinct

import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;

public class OperacionesStream {
    public static void main(String[] args) {
        List<string> lista = List.of("Ana", "Luis", "María", "Juan");

        lista.stream().sorted().forEach(System.out::println);

        int maximo = lista.stream()
            .mapToInt(String::length)
            .max()
            .orElse(0);

        int minimo = lista.stream()
            .mapToInt(String::length)
            .min()
            .orElse(0);

        String frase = "hola hola mundo mundo java";
        List<string> unicas = Stream.of(frase.split(" "))
            .filter(p -> !p.isEmpty())
            .map(String::toLowerCase)
            .distinct()
            .sorted()
            .toList();
    }
}
</string></string>

reduce

import java.util.List;
import java.util.stream.Stream;

public class ReduccionEjemplo {
    public static void main(String[] args) {
        String concatenado = Stream.of("A", "B", "C").reduce("", String::concat);

        double minimo = Stream.of(-1.5, 2.3, 0.8).reduce(Double.MAX_VALUE, Double::min);

        List<integer> numeros = List.of(1, 2, 3, 4);
        int total = numeros.stream().reduce(0, (a, b) -> a + b);

        int totalSinSemilla = Stream.of(1, 2, 3, 4).reduce(Integer::sum).orElse(0);
    }
}
</integer>

Operaciones de coincidencia

import java.util.List;

class Producto {
    int id;
    public Producto(int id) { this.id = id; }
    public int getId() { return id; }
}

public class CoincidenciaEjemplo {
    public static void main(String[] args) {
        List<producto> productos = List.of(new Producto(2), new Producto(5), new Producto(8));

        boolean todos = productos.stream().allMatch(p -> p.getId() > 3);
        boolean alguno = productos.stream().anyMatch(p -> p.getId() > 3);
        boolean ninguno = productos.stream().noneMatch(p -> p.getId() > 10);

        System.out.println(todos);
        System.out.println(alguno);
        System.out.println(ninguno);
    }
}
</producto>

Streams especializados

Para trabajar con tipos primitivos sin incurrir en autoboxing, Java ofrece IntStream, LongStream y DoubleStream.

Creación y operaciones básicas

import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class StreamsPrimitivos {
    public static void main(String[] args) {
        IntStream.of(1, 2, 3, 4).forEach(System.out::println);

        IntStream.range(1, 5).forEach(System.out::println);
        IntStream.rangeClosed(1, 5).forEach(System.out::println);

        int[] datos = {1, 2, 3};
        IntStream streamArray = IntStream.of(datos);

        new Random().ints(5, 0, 100).forEach(System.out::println);
    }
}

Filtrado, mapeo y ordenación

IntStream.of(1, 2, 3, 4, 5)
    .filter(n -> n % 2 == 0)
    .forEach(System.out::println);

IntStream.of(1, 2, 3)
    .mapToLong(n -> (long) n * 10)
    .forEach(System.out::println);

IntStream.of(3, 1, 4, 2)
    .sorted()
    .forEach(System.out::println);

IntStream.of(1, 2, 2, 3, 3, 3)
    .distinct()
    .forEach(System.out::println);

Agregación y estadísticas

import java.util.IntSummaryStatistics;
import java.util.stream.IntStream;

public class EstadisticasEjemplo {
    public static void main(String[] args) {
        int suma = IntStream.of(1, 2, 3).sum();
        int maximo = IntStream.of(1, 2, 3).max().orElse(0);
        double promedio = IntStream.of(1, 2, 3).average().orElse(0);

        IntSummaryStatistics stats = IntStream.of(1, 2, 3, 4).summaryStatistics();
        System.out.println(stats.getSum());
        System.out.println(stats.getCount());
        System.out.println(stats.getAverage());
    }
}

Reduce y paralelismo

int factorial = IntStream.rangeClosed(1, 5)
    .reduce(1, (a, b) -> a * b);

long sumaParalela = IntStream.rangeClosed(1, 1_000_000)
    .parallel()
    .sum();

Referencias a métodos

Las referencias a métodos son una forma abreviada de expresiones Lambda cuando el cuerpo de la lambda simplemente invoca un método existente.

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

interface Parser {
    int ejecutar(String texto);
}

public class ReferenciasMetodos {
    public static void main(String[] args) {
        Parser parser1 = s -> Integer.parseInt(s);
        Parser parser2 = Integer::parseInt;

        System.out.println(parser2.ejecutar("123"));

        List<integer> valores = Arrays.asList(45, 12, 78, 3);
        valores.sort(Integer::compare);
        valores.forEach(System.out::println);
    }
}
</integer>

El operador :: permite referirse a métodos estáticos, métodos de instancia y constructores, reduciendo aún más la sintaxis.

Etiquetas: java lambda Stream API IntStream Method Reference

Publicado el 7-6 16:11