1. Overview
In this quick article, we explain different ways of merging Java Streams – which is not a very intuitive operation.
2. Using Plain Java
The JDK 8 Stream class has some useful static utility methods. Let’s take a closer look at the concat() method.
2.1. Merging Two Streams
The simplest way to combine 2 Streams is to use the static Stream.concat() method:
@Test public void whenMergingStreams_thenResultStreamContainsElementsFromBoth() { Stream<Integer> stream1 = Stream.of(1, 3, 5); Stream<Integer> stream2 = Stream.of(2, 4, 6); Stream<Integer> resultingStream = Stream.concat(stream1, stream2); assertEquals( Arrays.asList(1, 3, 5, 2, 4, 6), resultingStream.collect(Collectors.toList())); }
2.2. Merging Multiple Streams
When we need to merge more than 2 Streams, things become a bit more complex. One possibility is to concatenate the first two streams, then concatenate the result with the next one and so on.
The next code snippet shows this in action:
@Test public void given3Streams_whenMerged_thenResultStreamContainsAllElements() { Stream<Integer> stream1 = Stream.of(1, 3, 5); Stream<Integer> stream2 = Stream.of(2, 4, 6); Stream<Integer> stream3 = Stream.of(18, 15, 36); Stream<Integer> resultingStream = Stream.concat( Stream.concat(stream1, stream2), stream3); assertEquals( Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36), resultingStream.collect(Collectors.toList())); }
As we can see, this approach becomes unfeasible for more streams. Of course, we can create intermediate variables or helper methods to make it more readable, but here is a better option:
@Test public void given4Streams_whenMerged_thenResultStreamContainsAllElements() { Stream<Integer> stream1 = Stream.of(1, 3, 5); Stream<Integer> stream2 = Stream.of(2, 4, 6); Stream<Integer> stream3 = Stream.of(18, 15, 36); Stream<Integer> stream4 = Stream.of(99); Stream<Integer> 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())); }
What happens here is:
- We first create a new Stream containing the 4 Streams, which results in a Stream<Stream<Integer>>
- Then we flatMap() this into a Stream<Integer> using the identity function
3. Using StreamEx
StreamEx is an open-source Java library that extends possibilities of Java 8 Streams. It uses the StreamEx class as an enhancement to the JDK’s Stream interface.
3.1. Merging Streams
The StreamEx library allows us to merge streams using the append() instance method:
@Test public void given4Streams_whenMerged_thenResultStreamContainsAllElements() { Stream<Integer> stream1 = Stream.of(1, 3, 5); Stream<Integer> stream2 = Stream.of(2, 4, 6); Stream<Integer> stream3 = Stream.of(18, 15, 36); Stream<Integer> stream4 = Stream.of(99); Stream<Integer> 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())); }
Since it is an instance method, we can easily chain it and append multiple streams.
Note that we could also create a List out of the stream by using toList() if we type the resultingStream variable to the StreamEx type.
3.2. Merging Streams Using prepend()
StreamEx also contains a method that adds elements before one another called prepend():
@Test public void given3Streams_whenPrepended_thenResultStreamContainsAllElements() { Stream<String> stream1 = Stream.of("foo", "bar"); Stream<String> openingBracketStream = Stream.of("["); Stream<String> closingBracketStream = Stream.of("]"); Stream<String> resultingStream = StreamEx.of(stream1) .append(closingBracketStream) .prepend(openingBracketStream); assertEquals( Arrays.asList("[", "foo", "bar", "]"), resultingStream.collect(Collectors.toList())); }
4. Using jOOλ
jOOλ is a JDK 8 compatible library that provides useful extensions to the JDK. The most important stream abstraction here is called Seq. Note that this is a sequential and ordered stream, so calling parallel() will have no effect.
4.1. Merging Streams
Just like the StreamEx library, jOOλ has an append() method:
@Test public void given2Streams_whenMerged_thenResultStreamContainsAllElements() { Stream<Integer> seq1 = Stream.of(1, 3, 5); Stream<Integer> seq2 = Stream.of(2, 4, 6); Stream<Integer> resultingSeq = Seq.ofType(seq1, Integer.class) .append(seq2); assertEquals( Arrays.asList(1, 3, 5, 2, 4, 6), resultingSeq.collect(Collectors.toList())); }
Also, there is a convenience toList() method if we type the resultingSeq variable to the jOOλ Seq type.
4.2. Merging Streams with prepend()
As expected, since an append() method exists, there is also a prepend() method in jOOλ:
@Test public void given3Streams_whenPrepending_thenResultStreamContainsAllElements() { Stream<String> seq = Stream.of("foo", "bar"); Stream<String> openingBracketSeq = Stream.of("["); Stream<String> closingBracketSeq = Stream.of("]"); Stream<String> resultingStream = Seq.ofType(seq, String.class) .append(closingBracketSeq) .prepend(openingBracketSeq); Assert.assertEquals( Arrays.asList("[", "foo", "bar", "]"), resultingStream.collect(Collectors.toList())); }
5. Conclusion
We saw that merging streams is relatively straightforward using JDK 8. When we need to do a lot of merging, it might be beneficial to use the StreamEx or jOOλ library for the sake of readability.
You can find the source code on GitHub.