Stream API: The Hero Without a Cape

Hey Tea Lovers! In this post, we will be talking about an unsung hero of the Java world, the Stream API which was added to Java 8 and it has changed the way we do programs in java and how! It added the neatness as well as made the program more readable. It helps you do functional programming in Java. Without any further ado, let us read more about this jewel.

What is Stream API

The Stream API, included in Java 8, is utilized for processing collections of objects. It is the flow of Objets on which various methods get applied in the pipeline to have the result. Simply put, it flows through the given Collection<Object> and applies the different methods on the Object to aggregate them into the desired result without affecting the original Object.

What Stream Provides

Stream doesn’t store anything instead, it operates on the given CollectionArray or I/O (yes you can use it on I/O) Without changing the original data, it applies the methods to the data. Lazy evaluation helps to add multiple intermediate operations without actually utilizing it at the moment of building it. It gets evaluated only after we terminal operation. The Stream API also adds neatness in the code to make it more readable. And yes, Functional Programming is a key feature.

Things to Know Before You Go

Stream allows you to do functional programming in Java. Which means you should be comfortable with functional interfaces and lambda. Don’t worry, You just need basics of it, which you can do with the help of this post, “Be More Functional With Java’s Functional Interfaces”. If you know these well, nobody will stop you from implementing the Stream API. The examples used in this post, you can find the code on GitHub here or the full project here. And yes, fill your cup of tea, start sipping and see more about this hero.

How to Stream

We can do this in multiple ways. Such magic tricks are,

  1. Collection’s stream() and parallelStream() methods
  2. Via Arrays.stream(Object[])
  3. One of Stream’s static factory methods such as,
    1. Stream.of(Object[])
    2. IntStream.range(int, int) 
    3. Stream.iterate(Object, UnaryOperator)
  4. Streaming the line of a file by BufferedReader.lines()
  5. Streams of random numbers can be obtained from Random.ints()

1st and 2nd are what we use most often. For our discussion, we will be sticking to the Collection’s stream method. For Example,

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
list.stream()
	// multiplying each number by 2
	.map(n -> n * 2)
	//taking forward only even numbers
	.filter(n -> n % 2 == 0)
	//printing each even number
	.forEach(n -> System.out.println(n));

Stream Pipeline Operations

We can perform two operations on a Stream pipeline

  • Intermediate Operation
  • Terminal Operation

Intermediate Operation transforms or filters the object in the preceding pipeline. These methods are nothing but the functional interfaces we discussed in our previous post. You should get familiar with them to easily understand this operation without any trouble. Stream won’t execute intermediate operations unless a Terminal Operation gets called.

Terminal Operation is an indication for the Stream to start the pipeline. Once we call the terminal operation pipeline ends and it returns the desired result. After the terminal operation, we cant use intermediate operation since the pipeline has ended. It can only be called once at the end of the pipeline. However, you can again start the stream if the terminal operation returns a collection. Which I highly oppose doing.

You can pretty much everything inside a single stream pipeline only.

Now, let us see these operations one by one.

Stream API Intermediate Operations

In the Stream pipeline, we can have multiple intermediate operations that transform or filter the data which will be flowing towards the next pipe without affecting the actual data on the Collection. These operations return the Stream of the desired result instead of the actual result i.e Stream<T>. With this, we can add more intermediate operations without breaking the Stream pipeline. We will be talking about the map and filter in this post and others in another part. map and filter are the ones you will be needing most of the time.

These methods are applied one by one to the elements in the pipeline and not all at once.

Stream map()

Think of it as a transforming function, which transforms the given object to another. It accepts a Function<T> as a parameter, which takes the element in the stream as input, performs some operation, we tell it to do, and returns the Stream of the resulting object. Learn more about Function here.

If we look at our previous example, it is multiplying each number by 2. We can have another map which can add 2 to the multiplied number and then another one to convert it to another object. and then another one and so on, you get the idea.

    list.stream()
        // 1. multiplying each number by 2
        .map(n -> n * 2)
        // 2. Converting to string.
        .map(n -> "CodersTea.com-Post No : " + n)
        //3. Now you will be working on the
        //stream of String
        //4. we add another string to the previous
        .map(string -> string + ". Another String")
        //5. print the end result
        .forEach(System.out::println);

In the above example look how we transformed a list of Integer to the String. Lets breakdown it.

  1. Multiplying each number by 2. So 1 becomes 2, 4 becomes 8
  2. We have attached the number to a string resulting in a string object.
  3. The previous map made the Stream<Integer> to Stream<String> due to the return type of string.
  4. We again attached another string to perform some operation.
  5. Finally printing the results. Which looks like,

Output:

CodersTea.com-Post No : 2. Another String
CodersTea.com-Post No : 4. Another String
CodersTea.com-Post No : 6. Another String
CodersTea.com-Post No : 8. Another String
CodersTea.com-Post No : 10. Another String
CodersTea.com-Post No : 12. Another String
CodersTea.com-Post No : 14. Another String
CodersTea.com-Post No : 16. Another String
CodersTea.com-Post No : 18. Another String
CodersTea.com-Post No : 20. Another String

Stream filter()

The filter, as the name suggests, filter outs the elements before it reaches the next pipe. Simply put, if the element satisfies the condition it allows it to pass through otherwise it won’t allow for the next operation. It takes a Predicate as a parameter. Know about Predicate here.

long count = list.stream()
        // 1. Need only even numbers
        .filter(n -> n % 2 == 0)
        // 2. Checking if the Number is
        // greater than 5
        .filter(n -> n > 5)
        //3. multiplying the number by 5
        // just to show how we can use multiple
        // Intermediate operation together
        .map(n -> n * 5)
        // 4. Any number less than 50
        .filter(n -> n < 50)
        // Counting how much elements
        // survived teh pipeline
        .count();
System.out.println("Total " + count + " numbers survived the storm");

God, so many filters. let’s break this down, shall we?

  1. Take only even numbers ahead.
  2. Forward only those numbers which are greater than 5
  3. Multiply the number by 5. Again, just to show you can have multiple Intermediate operations in a single Stream pipeline.
  4. Allow numbers that are less than 50.
  5. Finally, count (Terminal Operation) how many survived after this much filtering.

Output:

Total 2 numbers survived the storm

Stream API Terminal Operation

Till now we have seen the operations which perform something in the pipe and return the stream. In the end, we will be needing a final result, which is not a stream, don’t we? That’s where Terminal Operation comes. It does not only give the desired result but also starts the Stream. As I said, Stream is lazy, it won’t do anything, no matter how many intermediate operations we add to the pipeline, until and unless it sees its end, the terminal operation.

You cant use the same stream after it was closed by a terminal operation. Otherwise, it will throw IllegalStateException: stream has already been operated upon or closed. You have to start a new stream

The terminal operations we will be using for our examples are forEach and count. Let us examine them and a few others.

Stream forEach()

This iterates over each element passed by preceding intermediate operation. It takes input but hs void return value. It takes Consumer as a parameter(more on Consumer). Use this if you want to do something without returning anything. We have used it for printing the numbers.

list.stream()
        // square of each number
        .map(n -> n * n)
        //prints each element
        .forEach(n -> System.out.print(" " + n));

Stream count()

As the name suggests, it is used for counting the elements reached to the end. Like in filter example, counting the number who satisfied all the filter’s conditions.

long evenAndLt5Count = list.stream()
        // even numbers less than 5s
        .filter(n -> n < 5 && n % 2 == 0)
        .count();
System.out.println(evenAndLt5Count);

Stream collect()

I use this more often than any other terminal operation. It allows you to create the Collection from the elements of the stream after processing them. These Collection can be ListSet or Map. The Collection includes elements that passed through the last pipe.

List<Integer> processedList = list.stream()
        // multiplying by 5
        .map(n -> n * 5)
        // numbers less than 30
        .filter(n -> n < 30)
        //add  +2
        .map(n -> n + 2)
        // give the list of processed numbers
        .collect(Collectors.toList());
System.out.println("processed List is \n " + processedList);

In the pipeline we have multiplied the number by 5, then filtered numbers less than 30 and added 2. And after all the elements are done process, we collect them into the list.

processed List is [7, 12, 17, 22, 27]

The Last Sip

We have studied what is Stream API in java, how to obtain it, how to operate it, use it. This post is kind of like an introductory post for the Stream API. There are so many diamonds in the Stream mine, which we will be mining in the next post. I highly recommend you check out the functional interface post for better understanding the Stream API operation. You can find the code on GitHub here or the full project here. See you in the next post.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free Web Hosting