Expresiones Lambda en Java: Guía Esencial para Recorridos y Fechas
Las expresiones lambda son una de las características más importantes introducidas en Java 8. Nos permiten escribir código más conciso y legible, especialmente al trabajar con colecciones y fechas. Esta guía te ayudará a comprender y utilizar las expresiones lambda más comunes en el desarrollo diario.
Qué son las Expresiones Lambda
Una expresión lambda es una función anónima que podemos pasar como argumento a métodos. Nos permite tratar la funcionalidad como si fuera un dato, haciendo nuestro código más flexible y expresivo.
Sintaxis Básica
La sintaxis de una lambda consta de tres partes:
(parámetros) -> { cuerpo de la función }
Ejemplo simple:
// Forma tradicional con clase anónima
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Hola Mundo");
}
};
// Forma con lambda (más concisa)
Runnable r2 = () -> System.out.println("Hola Mundo");
Recorrido de Colecciones con Lambdas
Recorrer Arrays
Los arrays se pueden recorrer de manera elegante utilizando lambdas y Streams:
String[] nombres = {"Ana", "Carlos", "Beatriz", "David"};
// Recorrido con forEach y lambda
Arrays.stream(nombres)
.forEach(nombre -> System.out.println("Hola " + nombre));
// O con referencia a método (method reference)
Arrays.stream(nombres)
.forEach(System.out::println);
Recorrer Listas
Las listas son quizás donde más utilizamos las expresiones lambda:
List<String> frutas = Arrays.asList("Manzana", "Banana", "Naranja", "Fresa");
// Recorrido básico
frutas.forEach(fruta -> System.out.println("Me gusta la " + fruta));
// Recorrido con números de posición
IntStream.range(0, frutas.size())
.forEach(i -> System.out.println(i + ": " + frutas.get(i)));
// Filtrar y recorrer
frutas.stream()
.filter(fruta -> fruta.length() > 6)
.forEach(fruta -> System.out.println("Fruta larga: " + fruta));
Recorrer Mapas
Los mapas también se benefician enormemente de las lambdas:
Map<String, Integer> edades = new HashMap<>();
edades.put("Ana", 25);
edades.put("Carlos", 32);
edades.put("Beatriz", 28);
// Recorrer entry set (clave-valor)
edades.forEach((nombre, edad) ->
System.out.println(nombre + " tiene " + edad + " años")
);
// Recorrer solo las claves
edades.keySet().forEach(clave ->
System.out.println("Persona: " + clave)
);
// Recorrer solo los valores
edades.values().forEach(valor ->
System.out.println("Edad: " + valor)
);
Manejo de Fechas con Lambdas
El API de fecha y hora introducido en Java 8 funciona perfectamente con lambdas.
Trabajar con LocalDate
LocalDate representa una fecha sin hora:
// Crear fechas
LocalDate hoy = LocalDate.now();
LocalDate navidad = LocalDate.of(2025, 12, 25);
// Operaciones con fechas usando streams
List<LocalDate> fechasImportantes = Arrays.asList(
LocalDate.of(2025, 1, 1),
LocalDate.of(2025, 5, 10),
LocalDate.of(2025, 9, 16),
LocalDate.of(2025, 12, 25)
);
// Filtrar fechas futuras
fechasImportantes.stream()
.filter(fecha -> fecha.isAfter(hoy))
.forEach(fecha -> System.out.println("Fecha futura: " + fecha));
// Formatear fechas
fechasImportantes.forEach(fecha ->
System.out.println(fecha.format(DateTimeFormatter.ofPattern("dd 'de' MMMM 'de' yyyy")))
);
Trabajar con LocalTime
LocalTime representa una hora sin fecha:
// Crear horas
LocalTime ahora = LocalTime.now();
LocalTime almuerzo = LocalTime.of(14, 30);
// Lista de horas del día
List<LocalTime> horas = Arrays.asList(
LocalTime.of(8, 0), // Inicio de trabajo
LocalTime.of(12, 0), // Almuerzo
LocalTime.of(14, 30), // Regreso de almuerzo
LocalTime.of(18, 0) // Fin de trabajo
);
// Filtrar horas laborales
horas.stream()
.filter(hora -> hora.isBefore(almuerzo))
.forEach(hora -> System.out.println("Mañana: " + hora));
// Convertir a minutos del día
horas.forEach(hora ->
System.out.println("Minutos del día: " + hora.toSecondOfDay() / 60)
);
Trabajar con LocalDateTime
LocalDateTime combina fecha y hora:
// Crear fechas con hora
LocalDateTime ahoraCompleto = LocalDateTime.now();
LocalDateTime reunion = LocalDateTime.of(2025, 11, 15, 10, 30);
// Lista de eventos
List<LocalDateTime> eventos = Arrays.asList(
LocalDateTime.of(2025, 11, 8, 9, 0), // Hoy, reunión matutina
LocalDateTime.of(2025, 11, 15, 10, 30), // Próxima reunión
LocalDateTime.of(2025, 12, 25, 0, 0) // Navidad
);
// Filtrar eventos del mes actual
eventos.stream()
.filter(evento -> evento.getMonth() == ahoraCompleto.getMonth())
.forEach(evento -> System.out.println("Evento este mes: " + evento));
// Ordenar eventos por fecha
eventos.stream()
.sorted()
.forEach(evento -> System.out.println(
"Evento: " + evento.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"))
));
Ejemplos Prácticos Combinados
Procesar Lista de Personas con Fechas
// Clase Persona
class Persona {
private String nombre;
private LocalDate fechaNacimiento;
// constructor, getters...
}
List<Persona> personas = Arrays.asList(
new Persona("Ana", LocalDate.of(1995, 6, 15)),
new Persona("Carlos", LocalDate.of(1990, 12, 3)),
new Persona("Beatriz", LocalDate.of(1998, 8, 20))
);
// Calcular edades y filtrar mayores de edad
LocalDate hoy = LocalDate.now();
personas.stream()
.filter(p -> Period.between(p.getFechaNacimiento(), hoy).getYears() >= 18)
.forEach(p -> {
int edad = Period.between(p.getFechaNacimiento(), hoy).getYears();
System.out.println(p.getNombre() + " tiene " + edad + " años");
});
Agrupar Fechas por Mes
// Agrupar fechas por mes usando lambdas
List<LocalDate> fechas = Arrays.asList(
LocalDate.of(2025, 1, 15),
LocalDate.of(2025, 1, 20),
LocalDate.of(2025, 2, 10),
LocalDate.of(2025, 2, 25),
LocalDate.of(2025, 3, 5)
);
Map<Month, List<LocalDate>> fechasPorMes = fechas.stream()
.collect(Collectors.groupingBy(LocalDate::getMonth));
fechasPorMes.forEach((mes, listaFechas) -> {
System.out.println("Mes: " + mes);
listaFechas.forEach(fecha ->
System.out.println(" - " + fecha.format(DateTimeFormatter.ofPattern("dd/MM")))
);
});
Mejores Prácticas
Referencias a Método (Method References)
Cuando una lambda solo llama a un método existente, podemos usar referencias a método:
// En lugar de: nombre -> System.out.println(nombre)
nombres.forEach(System.out::println);
// En lugar de: fecha -> fecha.getMonth()
fechas.stream().map(LocalDate::getMonth).collect(Collectors.toList());
// En lugar de: (String s) -> s.length()
palabras.stream().mapToInt(String::length).sum();
Evitar Efectos Secundarios
Las lambdas deben ser puras cuando sea posible:
// ✅ Bien: Lambda pura
List<Integer> duplicados = numeros.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
// ❌ Evitar: Lambda con efectos secundarios
List<Integer> resultado = new ArrayList<>();
numeros.forEach(n -> {
n = n * 2;
resultado.add(n); // Modificar estado externo
});
Manejo de Excepciones
Las lambdas no pueden lanzar excepciones verificadas directamente:
// ✅ Manejo con try-catch dentro
fechas.forEach(fecha -> {
try {
// Código que puede lanzar excepción
LocalDate.parse(fecha.toString());
} catch (DateTimeParseException e) {
System.err.println("Fecha inválida: " + fecha);
}
});
// ✅ Crear método auxiliar
private static void procesarFecha(LocalDate fecha) throws DateTimeParseException {
// Código que puede lanzar excepción
}
fechas.forEach(LambdaFechasGUIA::procesarFecha); // Envuelve la excepción
Conclusión
Las expresiones lambda han transformado la forma en que escribimos código Java, haciéndolo más conciso, legible y funcional. Dominar su uso con colecciones y fechas es esencial para cualquier desarrollador Java moderno.
Recuerda practicar estos conceptos gradualmente, comenzando con ejemplos simples y avanzando hacia casos más complejos. Las lambdas no solo hacen tu código más elegante, sino que también abren la puerta a paradigmas de programación funcional dentro de Java.
Esta guía se basa en la documentación oficial de Oracle y las mejores prácticas establecidas en la comunidad Java. Para profundizar, consulta los tutoriales oficiales de Oracle sobre Expresiones Lambda y Stream API.