Merging Streams in Java: Una Guía Completa
En este artículo, explicaremos diferentes maneras de fusionar Streams en Java, una operación que no resulta muy intuitiva. Los Streams son una poderosa herramienta en Java que permite trabajar con secuencias de datos de manera fluida y mantenible. Sin embargo, fusionar múltiples Streams puede presentar ciertos retos. A lo largo de esta entrada, exploraremos varias maneras de hacerlo, desde métodos simples ofrecidos por la biblioteca estándar de Java hasta soluciones más complejas y potentes proporcionadas por bibliotecas adicionales como StreamEx y jOOλ.
1. Overview
La clase Stream de Java, introducida en Java 8, tiene métodos estáticos útiles para trabajar con secuencias de datos. A continuación, nos centraremos en cómo podemos fusionar diferentes Streams utilizando principalmente el método concat()
de la clase Stream.
2. Using Plain Java
2.1. Merging Two Streams
La forma más sencilla de combinar dos Streams es usando el método estático Stream.concat()
. Este método toma dos Streams como parámetros y devuelve un nuevo Stream que contiene todos los elementos de ambos.
@Test
public void whenMergingStreams_thenResultStreamContainsElementsFromBoth() {
Stream stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream resultingStream = Stream.concat(stream1, stream2);
assertEquals(
Arrays.asList(1, 3, 5, 2, 4, 6),
resultingStream.collect(Collectors.toList()));
}
En este código, creamos dos Streams de números enteros y, usando Stream.concat
, obtenemos un Stream resultante que contiene todos los elementos de ambos. El uso de assertEquals
verifica que el resultado es el esperado.
2.2. Merging Multiple Streams
Cuando necesitamos fusionar más de dos Streams, la operación se vuelve un poco más complicada. Una posibilidad es concatenar el primer par de Streams y luego fusionar el resultado con el siguiente, y así sucesivamente. Veamos cómo se realiza esto:
@Test
public void given3Streams_whenMerged_thenResultStreamContainsAllElements() {
Stream stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream stream3 = Stream.of(18, 15, 36);
Stream resultingStream = Stream.concat(Stream.concat(stream1, stream2), stream3);
assertEquals(
Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36),
resultingStream.collect(Collectors.toList()));
}
Sin embargo, este enfoque se vuelve poco manejable con más Streams. Una alternativa más eficiente y legible es utilizar flatMap
en una lista de Streams.
@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
Stream stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream stream3 = Stream.of(18, 15, 36);
Stream stream4 = Stream.of(99);
Stream resultingStream = Stream.of(stream1, stream2, stream3, stream4)
.flatMap(i -> i);
assertEquals(
Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99),
resultingStream.collect(Collectors.toList()));
}
En este ejemplo, primero creamos un nuevo Stream que contiene los cuatro Streams (lo que resulta en un Stream<StreamflatMap
para aplanar los Streams en un único Stream
3. Using StreamEx
StreamEx es una biblioteca de Java de código abierto que extiende las posibilidades de los Streams de Java 8. Utiliza la clase StreamEx como mejora de la interfaz Stream del JDK.
3.1. Merging Streams
La biblioteca StreamEx permite fusionar Streams usando el método de instancia append()
:
@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
Stream stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream stream3 = Stream.of(18, 15, 36);
Stream stream4 = Stream.of(99);
Stream resultingStream = StreamEx.of(stream1)
.append(stream2)
.append(stream3)
.append(stream4);
assertEquals(
Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99),
resultingStream.collect(Collectors.toList()));
}
Dado que se trata de un método de instancia, podemos encadenar fácilmente múltiples Streams. También se puede crear una List a partir del Stream usando toList()
si tipamos la variable resultante al tipo StreamEx.
3.2. Merging Streams Using prepend
La biblioteca StreamEx contiene un método que agrega elementos antes que otros, llamado prepend()
:
@Test
public void given3Streams_whenPrepended_thenResultStreamContainsAllElements() {
Stream stream1 = Stream.of("foo", "bar");
Stream openingBracketStream = Stream.of("[");
Stream closingBracketStream = Stream.of("]");
Stream resultingStream = StreamEx.of(stream1)
.append(closingBracketStream)
.prepend(openingBracketStream);
assertEquals(
Arrays.asList("[", "foo", "bar", "]"),
resultingStream.collect(Collectors.toList()));
}
Este método permite una manipulación más rica de los Streams, haciendo que la lógica sea más clara al agrupar adecuadamente elementos antes y después.
4. Using Jooλ
jOOλ es una biblioteca compatible con JDK 8 que proporciona extensiones útiles a la biblioteca estándar. La abstracción principal de Stream en jOOλ se llama Seq. Es importante destacar que este Stream es secuencial y ordenado, por lo que llamar a parallel()
no tendrá efecto.
4.1. Merging Streams
Al igual que la biblioteca StreamEx, jOOλ ofrece un método append()
:
@Test
public void given2Streams_whenMerged_thenResultStreamContainsAllElements() {
Stream seq1 = Stream.of(1, 3, 5);
Stream seq2 = Stream.of(2, 4, 6);
Stream resultingSeq = Seq.ofType(seq1, Integer.class)
.append(seq2);
assertEquals(
Arrays.asList(1, 3, 5, 2, 4, 6),
resultingSeq.collect(Collectors.toList()));
}
También existe un método de conveniencia toList()
si tipamos la variable resultante al tipo Seq de jOOλ.
4.2. Merging Streams With prepend
Como era de esperarse, dado que existe un método append()
, también hay un método prepend()
en jOOλ:
@Test
public void given3Streams_whenPrepending_thenResultStreamContainsAllElements() {
Stream seq = Stream.of("foo", "bar");
Stream openingBracketSeq = Stream.of("[");
Stream closingBracketSeq = Stream.of("]");
Stream resultingStream = Seq.ofType(seq, String.class)
.append(closingBracketSeq)
.prepend(openingBracketSeq);
Assert.assertEquals(
Arrays.asList("[", "foo", "bar", "]"),
resultingStream.collect(Collectors.toList()));
}
5. Conclusion
Hemos visto que fusionar streams es relativamente sencillo utilizando JDK 8. Sin embargo, cuando necesitamos realizar múltiples fusiones, puede ser beneficioso emplear bibliotecas como StreamEx o jOOλ. Estas bibliotecas no solo mejoran la legibilidad del código, sino que también ofrecen funcionalidades adicionales que resultan útiles en desarrollos más complejos.
Para los programadores que están buscando optimizar su trabajo con Streams en Java, considerar estas herramientas puede ser una elección acertada. Además, experimentar con distintos enfoques puede proporcionar nuevas perspectivas y mejores prácticas en el desarrollo de soluciones en Java.