How to use Streams API

Java 8 added a new feature called Streams. Streams represent a sequence of objects from a source. In this post, I show how to use Streams API.

Previously, using collections API, we would have a collection of objects and then a developer would process this collection to manipulate further to query. With Streams feature, the developer will not have to do any processing operation over the collection of objects.

Streams

Firstly, streams provide a set of elements in a sequential manner. It provides a number of APIs for aggregate operation. Streams take Arrays, Collections, or I/O sources as an input.

How Streams Work

A stream represents a sequence of elements. Stream operations are either intermediate or terminal. Intermediate operations return streams to process further while terminal operations return either void or non-stream results.

List<String> myList =
    Arrays.asList("test1", "sameresult", "netresult", "grossprofit", "test2");

myList
    .stream()
    .filter(s -> s.startsWith("test"))
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);

As shown above, filter, map, sorted are intermediate operations and forEach is a terminal operation. Javadocs provide the list of all operations on streams.

Most stream operations accept some kind of lambda expression parameter, a functional interface specifying the behavior of the operation.

Instead of using collections, you can also use Stream.Of() operation to create a stream from a bunch of objects.

Intermediate operations have a characteristic of laziness. To look at this, let’s check the example below:

Stream.of("n1", "n2", "n3", "n4", "n5")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    });

It will not print anything on the console. Intermediate operations will work only when there are terminal operations.

Once you call a terminal operation on streams, streams can not be reused.

Operations on Streams

Streams API offers aggregate operations that offer flexibility in using streams. I will show an example here about how to use streams

List<String> listOfStrings = new ArrayList<>();
listOfStrings.add("one");
listOfStrings.add("two");
listOfStrings.add("three");
listOfStrings.add("");
listOfStrings.add("four");

List<String> listOfNotEmptyStrings = listOfStrings.streams().filter(str -> !str.isEmpty()).collect(Collectors.toList());

In the example shown above, I have list of strings that I filter to get only a list containing non-empty strings.

Streams also offer forEach operation which can be used to iterate over the elements of the stream.

Collect is a terminal operation that can transform the elements of a stream into a different kind of result. Example – a List, Map, or a Set.

Map is an operation that allows us to transform objects of a stream into another type of object.

Reduce operation combines all elements of the stream into a single result.

ParallelStreams

Streams also offer something called ParallelStreams . Basically all operations performed over ParallelStreams ,are performed in parallel. Depending on your usage, use these streams carefully as they can cause concurrency issues.

Example – listOfStrings.parallelStream()

ParallelStreams can be used to improve the runtime performance on a large set of input elements.

Conclusion

In this post, I showed how to use Streams API in Java 8. Streams make it handy in many cases where we have a list of objects and we are processing these objects. If you enjoyed this post, subscribe to my blog here.