Sunday, August 22, 2021

How to debug Java 8 Stream Pipeline - peek() method Example Tutorial

Hello guys, I have been writing about some important methods from Java SE 8  like map(), flatMap(), collect(), etc for quite some time, and today I'll share my experience with another useful method peek() from java.utill.stream.Stream class. The peek() method of the Stream class can be very useful to debug and understand streams in Java 8. You can use the peek() method to see the elements as they flow from one step to another like when you use the filter() method for filtering, you can actually see how filtering is working like lazy evaluation as well as which elements are filtered.

The peek() method returns a stream consisting of the elements of this stream and performs the action requested by the client. The peek() method expects a Consumer functional interface to perform a non-interfering action on the elements of this stream, usually printing them using the forEach() method.

Btw, the sole reason for using peek() is debugging the Stream pipeline, even the API itself says that peek() is only there for debugging, but it does help to understand the lazy evaluation technique Stream uses to improve performance.

Lazy evaluation means nothing is evaluated in the Stream until a terminal method like forEach(), collect(), or reduce() is called and processing stops as soon as the result is obtained, which means not all the elements of Stream is processed always.

It all depends upon what kind of result you want from Stream. For example, if you call the findFirst() method then as soon as it finds the first element fulling the criterion, processing stops. If you want to understand lazy evaluation in-depth and other Stream features then I highly recommend you check out these Java collections and Stream courses from Udemy and Pluralsight. 




Java 8 Stream peek() method Example

In order to understand the peek() method better, let's see some code in action. How about using the filter and map methods in a chained pipeline?

This is a very common code in Java 8 and will help you to learn how stream pipeline processing works in Java 8? What happens in each step? What is the output or data in the stream after each step etc?

Consider the following example, which calls the peek() method after each step in a Stream pipeline involving filter() and map()  methods:

List<String> result = Stream.of("EURO/INR", "USD/AUD", "USD/GBP", "USD/EURO")
        .filter(e -> e.length() > 7)
        .peek(e -> System.out.println("Filtered value: " + e))
        .map(String::toLowerCase)
        .peek(e -> System.out.println("Mapped value: " + e))
        .collect(Collectors.toList());

In this example, we have a Stream of String and then we are filtering all Strings whose length is greater than 7 and then we are converting them to lowercase using the map() function.

Now, what do you think, how will this program execute? top to bottom or bottom to top?

Many of you will think that after the first filter() execution you will get a Stream containing two elements "EURO/INR" and "USD/EURO" and peek() will print those two elements.

Well, that's not the case, since Streams are executed lazily, nothing will happen until the collect() method will execute, which is the terminal method.

This is proved by the following output from running the above code into Eclipse IDE or command prompt, it will print the following lines:
Filtered value: EURO/INR
Mapped value: euro/inr
Filtered value: USD/EURO
Mapped value: usd/euro

The key point to note here is that values are filtered and mapped one by one, not together. It means the code is executed backward when the collect() method calls the Collectors.toList() to get the result in a List, it asks map() function which in turn asks the filter() method.

Since filter() is lazy it returns the first element whose length is greater than 7 and sits back until map() asks again.

You can see that peek() method clearly prints the value of the stream in the pipeline after each call to filter() method. You can further join From Collections to Streams in Java 8 Using the Lambda Expressions course on Pluralsight to learn more about different types of operation with Streamlike intermediate and terminal operation.

How to debug Java 8 Stream Pipeline - peek() method Example Tutorial





How to use peek() method in Java 8

As I said, the Stream.peek() method is very useful for debugging and understating the stream-related code in Java. Here is a couple of more example of using peek() to understand how bulk data operations are processed by Stream utility.

package test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Java 8 peek() method example
 */
public class Test {

    public static void main(String[] args) {

        List<String> versions = new ArrayList<>();
        versions.add("Lollipop");
        versions.add("KitKat");
        versions.add("Jelly Bean");
        versions.add("Ice Cream Sandwidch");
        versions.add("Honeycomb");
        versions.add("Gingerbread");

        // filtering all vaersion which are longer than 7 characters
        versions.stream()
                .filter(s -> s.length() > 7)
                .peek(e -> System.out.println("After the first filter: " + e))
                .filter(s -> s.startsWith("H"))
                .peek(e -> System.out.println("After the second filter: " + e))
                .collect(Collectors.toSet());

    }

}

Output
After the first filter: Lollipop
After the first filter: Jelly Bean
After the first filter: Ice Cream Sandwich
After the first filter: Honeycomb
After the second filter: Honeycomb
After the first filter: Gingerbread

By looking at this output, can you explain how the code would have been executed? Well, it seems that when to collect() ask the second filter() method, it further asks the first filter() and you can see that the first element Lollipop passed the first filter but couldn't pass the second one because it doesn't start with letter "H".

So, the second filter() again asks the first() filter for an element, it returns Jelly Bean, Ice Cream Sandwich, and HoneyComb one by one. Since HoneyComb made past the second filter it is collected by Collector and again the same process happens but aborted after GingerBread because all elements in Stream are already processed.

This clearly explains the lazy execution behavior of Stream as opposed to eager iterative implementation and the peek() method definitely helps you to understand this better, but if you want to learn Stream in-depth, I suggest you further check these Java Functional Programming and Stream API courses


Java 8 Stream peek() example for debugging


Important points

1) The peek() method of Stream class is an intermediate method, hence you can call other stream methods after this.

2) It returns a new Stream, which is basically the stream it got.

3) It accepts an object of functional interface Consumer to perform non-interfering action e.g. printing values.

4) For parallel stream pipelines, the action may be called at whatever time and whatever thread the element is made available by the upstream operation.

Btw, peek() is not the only way to figure out what goes inside a Stream pipeline, you can also use your IDEs to do the heavy work. For example, If you are using IntelliIDEA from JetBrains, you can also use their Java Stream Debugger Plugin to easily debug Java 8 code using map, filter, and collect in IDE itself, like shown in the following GIF diagram:

How to debug Java 8 code with map and filter



That's all about how to use the peek() method in Java 8. You can use the peek() method for debugging. It allows you to see the elements as they flow past a certain point in the pipeline. By using this you can check whether your filter() method is working properly or not. You can see exactly which elements are got filtered by using peek() in Java 8.



Related Java 8 Tutorials and Examples you may like
  • 5 Free Courses to learn Java 8 and 9 (courses)
  • 5 Books to Learn Java 8 Better? (read here)
  • 10 Examples of converting a List to Map in Java 8 (see here)
  • Java 8 Comparator Example (check here)
  • 10 Advanced Core Java courses for Programmers (courses)
  • Collection of best Java 8 tutorials (click here)
  • Best Courses to learn Java Programming for Beginners (best courses)
  • 10 Examples of Stream in Java 8 (example)
  • 7 best Courses to learn Data structure and Algorithms (best courses)
  • Difference between abstract class and interface in Java 8? (answer)
  • Difference between Stream.map() and Stream.flatMap() in Java 8? (answer)
  • How to sort the may by values in Java 8? (example)
  • 10 Free Courses for Experienced Java Programmers (courses)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • Top 5 Course to master Java 8 Programming (courses)

Thanks for reading this article so far. If you like this Java 8 filter method tutorial then please share it with your friends and colleagues. If you have any questions or feedback then please drop a note.

P. S. -  If you just want to learn more about new features in Java 8 then please see these Java 8 Online courses. It explains all the important features of Java 8 like lambda expressions, streams, functional interfaces, Optional, new Date Time API, and other miscellaneous changes.

3 comments:

  1. I'm not sure why the inconsistency between the 2 examples?
    In example 1, there is a List (result) and intermediate filtering functions are immediately called on it.
    In example 2, there is an ArrayList (version) and the "stream" function is called before filtering?

    They both implement the Collection interface. Also, I haven't been able to find the Stream.of static method either.

    ReplyDelete
    Replies
    1. Hello Joe, in first example, there is no list, in fact we have directly created a Stream by using Stream.of() method. In the second example, we have retrieved Stream by calling stream() method on an ArrayList object. The key thing they demonstrate is how filtering works in Stream, you can choose whatever you want to implement stream example. Btw, where did you look for Stream.of() function?

      Delete

Feel free to comment, ask questions if you have any doubt.