Transforma tus bucles anidados en Streams de Java: Guía Práctica y Moderna


Transforma tus bucles anidados en Streams de Java: Guía Práctica y Moderna
La API de Streams, introducida en Java 8, revolucionó la forma en que procesamos datos. Gracias a ella, podemos escribir código más limpio, declarativo y eficiente. En este artículo aprenderás a transformar bucles anidados en Streams, con ejemplos paso a paso que te harán amar la programación funcional en Java. 💡
🚀 1. ¿Qué es un Stream en Java?
Un Stream
es una secuencia de elementos que se procesan de forma funcional y perezosa desde una fuente (colección, array o archivo). A diferencia de las colecciones, los Streams no almacenan datos, sino que los transforman.
Las operaciones en Streams se dividen en:
- Intermedias: como
map()
,filter()
yflatMap()
. - Terminales: como
collect()
,forEach()
,findFirst()
.
Por ejemplo, flatMap()
es ideal para trabajar con estructuras anidadas, como listas de listas, y transformarlas en un único flujo plano.
🔁 2. Iteración Básica: Pares de dos listas
Versión con bucles:
public static List<int[]> obtenerParesImperativo(List<Integer> lista1, List<Integer> lista2) {
List<int[]> pares = new ArrayList<>();
for (Integer a : lista1) {
for (Integer b : lista2) {
pares.add(new int[]{a, b});
}
}
return pares;
}
Versión con Streams:
public static List<int[]> obtenerParesStream(List<Integer> lista1, List<Integer> lista2) {
return lista1.stream()
.flatMap(a -> lista2.stream().map(b -> new int[]{a, b}))
.collect(Collectors.toList());
}
Este enfoque es más limpio y expresivo, especialmente en operaciones complejas.
🎯 3. Añadiendo Condiciones: Filtrar resultados
Bucles con condición:
public static List<int[]> filtrarParesImperativo(List<Integer> lista1, List<Integer> lista2) {
List<int[]> pares = new ArrayList<>();
for (Integer a : lista1) {
for (Integer b : lista2) {
if (a + b > 7) {
pares.add(new int[]{a, b});
}
}
}
return pares;
}
Streams con filter()
:
public static List<int[]> filtrarParesStream(List<Integer> lista1, List<Integer> lista2) {
return lista1.stream()
.flatMap(a -> lista2.stream().map(b -> new int[]{a, b}))
.filter(par -> par[0] + par[1] > 7)
.collect(Collectors.toList());
}
Separamos la lógica de iteración y filtrado, haciendo el código más modular y fácil de leer.
⛔ 4. Interrupción Temprana: Encontrar el primer resultado
Con bucles y break
:
public static Optional<int[]> primerParValidoImperativo(List<Integer> lista1, List<Integer> lista2) {
for (Integer a : lista1) {
for (Integer b : lista2) {
if (a + b > 7) {
return Optional.of(new int[]{a, b});
}
}
}
return Optional.empty();
}
Con findFirst()
:
public static Optional<int[]> primerParValidoStream(List<Integer> lista1, List<Integer> lista2) {
return lista1.stream()
.flatMap(a -> lista2.stream().map(b -> new int[]{a, b}))
.filter(par -> par[0] + par[1] > 7)
.findFirst();
}
Gracias a findFirst()
, obtenemos una funcionalidad similar al break
, pero de forma más elegante y declarativa.
🧠 5. ¿Cuándo usar Streams?
✅ Úsalos cuando:
- Quieras mejorar la legibilidad del código.
- Necesites transformar y filtrar datos fácilmente.
- Busques procesamiento paralelo con
parallelStream()
.
❌ Evítalos si:
- La operación es extremadamente sencilla.
- El rendimiento es crítico y cada milisegundo cuenta.
- Necesitas depuración detallada paso a paso.
🎉 Conclusión
Transformar bucles anidados en Streams no solo hace tu código más limpio, sino también más mantenible y expresivo. Si bien no todos los casos ameritan usar Streams, en muchos escenarios pueden mejorar la calidad y claridad del desarrollo.
¡Anímate a refactorizar ese código viejo y dale un aire funcional a tus proyectos en Java! 💪🚀
Subscribe to my newsletter
Read articles from Carlos Exposito directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
