4 min read

Java Streams

Java's stream API is a powerful API for working with Java collections.
Java Streams

What Are Java Streams?

A Java stream is a sequence of elements. Streams don't store data. They allow you to perform functional-style operations on data. Once streams are consumed, they cannot be reused. Streams can be processed either sequentially or in parallel.

Java collections contain the method stream(). This method allows you to convert a collection into a stream and use the methods shown in this article.

The Most Common Methods

Most of what you need to know about Java streams comes from three different methods. These three methods allow you to accomplish pretty much anything you need to do.

Iterating Over the Elements

To iterate over a stream, you use the forEach() method. This is the same idea as a for-each loop. It is done by passing in a Consumer function into the forEach method.

items.forEach(
    item -> System.out.println(item)
);

Filtering Elements

You can filter elements in a stream using the filter() method.

List.of(1, 2, 3, 4, 5)
    .stream()
    .filter(i -> i % 2 == 0)

If the function in the filter() method returns true, it will end up in the resulting stream. This filters even numbers. The resulting stream has the values [2, 4].

Converting to a Different Type

You can convert from one type to another using the map() method.

List.of(1, 2, 3)
    .stream()
    .map(String::valueOf)

This maps each integer type to a string type using the String.valueOf() method.

Converting a Stream

You can convert a stream to a different collection, such as a map or a list. There are two different ways to do this, depending on whether you want a mutable or immutable collection.

Immutable Collections

If you want an immutable collection, you can use either the toList() or toSet() methods.

final List<String> immutable = Stream.of("a", "b", "c")
    .toList();

Mutable Collections

If you are wanting a mutable collection, use the collect() method and pass in a value from the Collectors class.

final var list = collection.stream()
    .collect(Collectors.toList());

The Collectors class has several other useful methods, such as toSet() and toMap(). Using toMap(), you pass in two functions, one for the key and one for the value.

final Map<Integer, String> items = Stream.of(1, 2, 3)
    .collect(
        Collectors.toMap(
            i -> i,                   // key
            i -> String.valueof(i)    // value
        )
    )

Flattening a Collection

If you are working with a collection of collections, you can flatten the collection by using the flatMap() method.

final var flatten = List.of(
    List.of(1, 2, 3(,
    List.of(4, 5, 6),
    List.of(7, 8, 9)
)
    .stream()
    .flatMap(Collection::stream)
    .toList();

This will result in a list that contains the values [1, 2, 3, 4, 5, 6, 7, 8, 9].

Joining a Stream

The Collectors.joining() method converts a stream to a string, such as a comma separated string.

final String value = Stream.of("a", "b", "c")
    .collect(Collectors.joining(","));

"a,b,c" will be the resulting string value.

Finding the First Item

Streams can be converted to an Optional of the first item in the Stream using the findFirst() method.

final Optional<String> first = items.stream()
    .findFirst();

This will return an empty Optional if the collection is empty, or it will return an Optional that has a value of the first item in the collection.

Streams of Numbers

When working with a stream of numbers, the reduce() method allows you to take a stream and reduce it down to one value. The reduce() method will return an Optional.

final var optional = List.of(1, 2, 3)
    .reduce((a, b) -> a + b);

This will return an Optional with the value of 6. If the stream is empty, the optional will be empty.

A second way to use reduce is to provide it with two parameters: an identity parameter, or the starting value, and a lambda. This version returns a value instead of an Optional.

final var sum = List.of(1, 2, 3)
    .reduce(0, (a, b) -> a + b);

If you were to change the first argument to 10, the sum would be 16 instead of 6, since the starting value is 10.

Java also has built-in methods for common operations such as sum(), min(), max(), and several others in the Integer class. You can use the reduce() method along with these methods.

final var sum = List.of(1, 2, 3)
    .reduce(0, Integer::sum);

Finding Out if a Predicate Matches

You can use streams to find out if any of the elements match a predicate function.

Any Match

You can check if any elements in the collection match a predicate using the anyMatch() method.

// true
final var matches = Stream.of(1, 2, 3, 4, 5)
    .anyMatch(i -> i == 2);

// false
final var noMatch = Stream.of(1, 2, 3, 4, 5)
    .anyMatch(i -> i == 9);

None Match

You can check if any elements in a collection don't match a predicate using the noneMatch() method.

// false
final var matches = Stream.of(1, 2, 3, 4, 5)
    .noneMatch(i -> i == 2);

// true
final var noMatch = Stream.of(1, 2, 3, 4, 5)
    .noneMatch(i -> i == 9);

All Match

You can check if all elements in a collection match a predicate using the allMatch() method.

// false
final var matches = Stream.of(1, 2, 3, 4, 5)
    .allMatch(i -> i > 10);

// true
final var noMatch = Stream.of(1, 2, 3, 4, 5)
    .allMatch(i -> i < 10);

Stream Ordering

You can change the order of a stream or a collection in a few different ways.

Sorting a Stream

You can sort a stream by using the sorted() method.

final var items = Stream.of(3, 5, 4, 2)
    .sorted()
    .toList();

This will result in a list where the values are ordered as [2, 3, 4, 5]. If the type doesn't implement the Comparable interface, a ClassCastException may be thrown.

You can pass in your own Comparator into the sorted function as well. The Comparator has several other methods that are useful for sorting on it as well.

Reversing a Collection

You can reverse the order of the elements in a collection using the reverseOrder() method on the Comparator interface.

final var items = Stream.of(3, 5, 4, 2)
    .sorted(Comparator.reverseOrder())
    .toList();

This will result in a list where the values are ordered as [2, 4, 5, 3].

Conclusion

There is much more you can do with Java streams. The methods covered in this article cover the common use cases when working with streams. The most important methods to be familiar with when working with streams are filter(), map(), and forEach().