Google Guava Streams

Introduction

Google Guava library offers many useful utility methods that simplify the coding. In this post, we will learn about the utilities offered in the Google Guava Streams class.

Note that many of the methods discussed here are marked as @Beta meaning that they could be altered in a breaking way or even removed in the future.

Using/Importing Google Guava

For Maven projects, you can import Google Guava from Maven as:

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>

For Gradle, add Google Guava as,

implementation 'com.google.guava:guava:30.1.1-jre'

Note: You can replace 30.1.1-jre with the latest version (or your preferred version) of Google Guava. However if you are using an older version than 30.1.1-jre, some of the methods discussed here may not be available.

Utilities for creating a Java Stream

There are several overloaded stream() static methods in the Google Guava Streams to help in creating a Java stream.

Creating a Stream from an Iterable and an Iterator

If we have an Iterable or Iterator, then we can use the stream() method to create a Java stream out of it.

List<Integer> list = List.of(1, 2, 10, 20, 30);
Iterable<Integer> iterable = list;
Streams.stream(iterable)
        .forEach(System.out::println);

In the above code, we are using the List<Integer> as the Iterable (as a List is an Iterable). Passing the Iterable to the stream() method returns a Java 8 stream and we use forEach to print the elements in the created stream.

Prints,

1
2
10
20
30

Similarly, we can pass an Iterator as well as shown below. It will result in the same output as before.

Iterator<Integer> iterator = list.iterator();
Streams.stream(iterator)
        .forEach(System.out::println);

Creating a Stream from Optional

There are overloaded stream() methods taking a Java OptionalOptionalInt, OptionalLong and OptionalDouble. It will create a stream with one value if the passed Optional is not empty. If the Optional is empty, it will create and return an empty stream.

Optional<Integer> optional = Optional.of(20);
Streams.stream(optional)
        .forEach(System.out::println); //20

optional = Optional.empty();
Streams.stream(optional)
        .forEach(System.out::println);  //empty stream

Similarly, for OptionalInt, OptionalLong and OptionalDouble, we have:

OptionalInt optionalInt = OptionalInt.of(10);
Streams.stream(optionalInt)
        .forEach(System.out::println); //10
        
OptionalLong optionalLong = OptionalLong.of(123456789L);
Streams.stream(optionalLong)
        .forEach(System.out::println); //123456789
        
OptionalDouble optionalDouble = OptionalDouble.of(2.3);
Streams.stream(optionalDouble)
        .forEach(System.out::println); //2.3

For,

However, if you are using Java 9+, you can use the stream() method on the Optional itself (Optional new methods in Java 9 to 11).

Optional.of(20).stream()
        .forEach(System.out::println);
OptionalInt.of(10).stream().forEach(System.out::println);
OptionalLong.of(123456789L).stream().forEach(System.out::println);
OptionalDouble.of(2.3).stream().forEach(System.out::println);

Stream concatenation

Concatenating Java streams

The Stream#concat takes a var-args of Java streams (Stream<? extends T>) and returns a Stream<T> which has the concatenated elements from the passed streams (in-order). In other words, the returned stream has all the elements from the first stream, followed by all the elements from the second stream, and so on.

Stream<Integer> stream1 = Stream.of(1, 2, 3);
Stream<Integer> stream2 = Stream.of(4, 5);
Stream<Integer> stream3 = Stream.of(6, 7);
Streams.concat(stream1, stream2, stream3)
        .forEach(System.out::println);

We create three streams of integers and concatenate using the Google Guava Streams concat() method. It returns a Stream<Integer> which we are printing using forEach on the stream. The result is:

1
2
3
4
5
6
7

When comparing this with the concat method on the Stream itself, the Java Stream method allows passing only two streams for concatenation. So, if we have multiple streams to concatenate, we have to concatenate pairs of streams. So, in this example, to concatenate the three streams using Stream#concat, it will look like,

//Java 8 concat
Stream.concat(Stream.of(1, 2, 3), Stream.concat(Stream.of(4, 5), Stream.of(6, 7)))
        .forEach(System.out::println);
//or
Stream.concat(Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5)), Stream.of(6, 7))
        .forEach(System.out::println);

Concatenating IntStream, LongStream and DoubleStream

There are overloaded concat() methods to concat IntStream, LongStream and DoubleStream. As seen before for concatenating Java stream, these methods take a var-args of IntStream, LongStream and DoubleStream, respectively.

//Concatenating IntStream
Streams.concat(
                IntStream.of(1, 2, 3),
                IntStream.of(4, 5),
                IntStream.of(6, 7))
        .forEach(System.out::println);

We are passing three IntStreams to the concat method and the result is:

1
2
3
4
5
6
7

Similarly, for concatenating LongStreams and DoubleStreams, we have,

//Concatenating LongStream
Streams.concat(
                LongStream.of(10, 20, 30),
                LongStream.of(40, 50),
                LongStream.of(60, 70))
        .forEach(System.out::println);

Output:

10
20
30
40
50
60
70
//Concatenating DoubleStream
Streams.concat(
                DoubleStream.of(1.1, 2.2, 3.3),
                DoubleStream.of(4.4, 5.5),
                DoubleStream.of(6.6, 7.7))
        .forEach(System.out::println);

Output:

1.1
2.2
3.3
4.4
5.5
6.6
7.7

Similar to the Stream#concat, even Java’s IntStream, LongStream and DoubleStream have concat method. But even they don’t take a var-args (they take only two streams). Example:

//Java 8 concat on IntStream - allow only 2 IntStreams to be passed
IntStream.concat(IntStream.of(1, 2, 3),
                IntStream.concat(IntStream.of(4, 5), IntStream.of(6, 7)))
        .forEach(System.out::println);

Streams#findLast

The Streams#findLast method takes a Stream<T> and returns the last element of the stream and an empty Optional if the stream is empty. If the stream has a non-deterministic order, then it could return any value (equivalent to Stream’s findAny method).

Optional<Integer> lastElement = Streams.findLast(Stream.of(1, 2, 3));
System.out.println(lastElement); //Optional[3]

//On an empty stream
System.out.println(Streams.findLast(Stream.empty())); //Optional.empty

Streams findLast on primitive specialization of streams

Let us see how findLast works on int, long and double specializations of streams. For IntStreams, it returns the last element of the stream as an OptionalInt and hence will return an empty OptionalInt if the stream is empty. Similarly, for LongStream and DoubleStreams, it returns an OptionalLong and OptionalDouble, respectively.

Shown below are examples for finding the last element of an IntStream, LongStream and a DoubleStream.

//IntStream
OptionalInt lastElementInt = Streams.findLast(IntStream.of(10, 20, 30));
System.out.println(lastElementInt); //OptionalInt[30]
System.out.println(Streams.findLast(IntStream.empty())); //OptionalInt.empty

//LongStream
OptionalLong lastElementLong = Streams.findLast(LongStream.of(1000, 2000, 3000));
System.out.println(lastElementLong); //OptionalLong[3000]
System.out.println(Streams.findLast(LongStream.empty())); //OptionalLong.empty

//DoubleStream
OptionalDouble lastElementDouble = Streams.findLast(DoubleStream.of(1.1, 2.2, 3.3));
System.out.println(lastElementDouble); //OptionalDouble[3.3]
System.out.println(Streams.findLast(DoubleStream.empty())); //OptionalDouble.empty

Streams#mapWithIndex

Map with index for a Stream

The mapWithIndex() method takes a Stream<T> and FunctionWithIndex instance and returns a new stream. Its signature is:

public static <T, R> Stream<R> mapWithIndex(
    Stream<T> stream, FunctionWithIndex<? super T, ? extends R> function)

where FunctionWithIndex interface is defined within the Google Guava Streams class as:

public interface FunctionWithIndex<T, R> {
  R apply(T from, long index);
}

The mapWithIndex() method applies the function passed to each element of the stream and creates a new stream with the mapped values (the result of applying the given function). The only difference between this and using a map on a stream is that the index is passed as an argument to the function. Thus, we can use this utility when we need the index of each element in the stream when performing the mapping operation.

Let us say we want to convert each element by appending the index to the value as shown below.

List<String> list = List.of("apple", "orange", "mango", "grapes");
Streams.mapWithIndex(list.stream(),
                (fruit, index) -> index + ":" + fruit)
        .forEach(System.out::println);

We pass the stream created out of the list to the mapWithIndex method and pass a lambda expression for the FunctionWithIndex instance. The function converts each fruit name by prefixing the index to it. The result is,

0:apple
1:orange
2:mango
3:grapes

Map with index for primitive specialization of streams

Similar to the other methods we have seen so far, it provides supports for mapping with index for an int, long and double streams as well. Their method signatures are as follows:

public static <R> Stream<R> mapWithIndex(IntStream stream, IntFunctionWithIndex<R> function)
public static <R> Stream<R> mapWithIndex(LongStream stream, LongFunctionWithIndex<R> function)
public static <R> Stream<R> mapWithIndex(DoubleStream stream, DoubleFunctionWithIndex<R> function)

And IntFunctionWithIndex, LongFunctionWithIndex and DoubleFunctionWithIndex are:

public interface IntFunctionWithIndex<R> {
  R apply(int from, long index);
}

public interface LongFunctionWithIndex<R> {
  R apply(long from, long index);
}

public interface DoubleFunctionWithIndex<R> {
  R apply(double from, long index);
}

Using these, examples are shown below:

//IntStream
Streams.mapWithIndex(IntStream.of(10, 20, 30),
                    (num, index) -> index + ":" + num)
            .forEach(System.out::println);
    System.out.println();

//Output
0:10
1:20
2:30

//LongStream
Streams.mapWithIndex(LongStream.of(1000, 2000, 3000),
                    (num, index) -> index + ":" + num)
            .forEach(System.out::println);
//Output
0:1000
1:2000
2:3000


//DoubleStream
Streams.mapWithIndex(DoubleStream.of(1.1, 2.2, 3.3),
                    (num, index) -> index + ":" + num)
            .forEach(System.out::println);
//Output
0:1.1
1:2.2
2:3.3

Streams forEachPair

The forEachPair method takes two Streams and a BiConsumer as shown below.

public static <A, B> void forEachPair(
    Stream<A> streamA, Stream<B> streamB, BiConsumer<? super A, ? super B> consumer)

It will invoke the consumer for each pair of corresponding elements in the passed streams.

Stream<String> stream1 = Stream.of("apple", "mango", "grapes");
Stream<Integer> stream2 = Stream.of(10, 12, 31);
Streams.forEachPair(stream1, stream2,
        (str, num) -> System.out.println(str + ":" + num));

We have two streams and we call forEachPair with a BiConsumer that just prints the pair passed to it. Resultant output is:

apple:10
mango:12
grapes:31

We can see that it passed each pair of elements from the two streams matching by the index to the consumer function passed.

Note: This method is closely related to the zip() method explained in the next section.

Streams of different length

If one of the streams is longer than the other, the extra elements will be ignored i.e., it calls the consumer as long as there are matching/corresponding elements from the two streams.

Stream<String> stream1 = Stream.of("apple", "mango", "grapes");
Stream<Integer> stream2 = Stream.of(10, 12);
Streams.forEachPair(stream1, stream2,
        (str, num) -> System.out.println(str + ":" + num));

Prints,

apple:10
mango:12

Streams zip

This is similar to the forEach method, it takes a BiFunction rather than a BiConsumer and returns a new stream.

public static <A, B, R> Stream<R> zip(
    Stream<A> streamA, Stream<B> streamB, BiFunction<? super A, ? super B, R> function)

For each pair of corresponding elements from the passed streams, it will call the BiFunction to get the mapped value. It will return a stream of the mapped values for each pair of elements.

Stream<String> stream1 = Stream.of("apple", "mango", "grapes");
Stream<Integer> stream2 = Stream.of(10, 12, 31);
Streams.zip(stream1, stream2,
                (str, num) -> str + ":" + num)
        .forEach(System.out::println);

The third argument is a BiFunction which produces a string as the output (which is computed by combining the two passed values). Output is:

apple:10
mango:12
grapes:31

Since we are using forEach on the returned stream to print the mapped values, the result is same as using forEachPair (where we printed the pairs of values using the BiConsumer).

As before, if one stream is shorter than the other, the rest of the values will be dropped/ignored.

Stream<String> stream1 = Stream.of("apple", "mango", "grapes");
Stream<Integer> stream2 = Stream.of(10, 12);
Streams.zip(stream1, stream2,
                (str, num) -> str + ":" + num)
        .forEach(System.out::println);
apple:10
mango:12

Conclusion

This concludes the post on Google Guava Streams class. Make sure to check out the other utilities offered by Google Guava.

References

Google Guava Streams Javadoc

Leave a Reply