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

I have been writing about some important methods from Java SE 8  like map(), flatMap(), collect() etc from quite some time and today I'll share my experience about another useful method peek() from java.utill.stream.Stream class. The peek() method of 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 e.g. 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 expect a Consumer functional interface to perform a non-interfering action on the elements of this stream, usually printing them using forEach() method.

Btw, the sole reason of using peek() is debugging 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 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, you can also check a comprehensive Java course like The Complete Java MasterClass or a specialized Java 8 books like Java 8 in Action by Manning publications.




Java 8 Stream peek() method Example

In order to understand 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 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 ask the filter() method.

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

You can see that peek() method clearly print the value of stream in the pipeline after each call to filter() method. You can further join From Collections to Streams in Java 8 Using Lambda Expressions course on Pluralsight to learn more about different types of operation with Stream e.g. 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 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 ask first() filter for an element, it returns Jelly Bean, Ice Cream Sandwich, and HoneyComb one by one. Since HoneyComb made passed the second filter it is collected by Collector and again the same process happens but aborted after GingerBread because all elements in Stream is already processed.

This clearly explains the lazy execution behavior of Stream as opposed to eager iterative implementation and peek() method definitely help you to understand this better, but if you want to learn Stream in depth, I suggest you to further check Refactoring to Java 8 Streams and Lambdas online course by Heinz Kabutz, a Java Champion .


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 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.


Further Learning
The Complete Java MasterClass
Java SE 8 for Really Impatient
From Collections to Streams in Java 8 Using Lambda Expressions


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)
  • Java 8 Interview Questions and Answers (questions)
  • 10 Examples of converting a List to Map in Java 8 (see here)
  • Java 8 Comparator Example (check here)
  • Collection of best Java 8 tutorials (click here)
  • 10 Examples of Stream in Java 8 (example)
  • 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)
  • 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 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 the course What's New in Java 8. It explains all the important features of Java 8 e.g. 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