Thursday, 25 April 2024

Debugging streams with peek method

Java streams, introduced in Java 8, improvise how the collections of objects are processed. Streams represent a sequence of elements that can be processed using operations provided by the Stream API. In general, streams act as wrappers around a data source like an array, collection, or I/O channel.

 

How do streams streamline processing?

Stream processing entails below steps:

a.   creating a stream from a data source such as collections, arrays etc.,

b.   Apply a sequence of intermediate operations—like filter(), map(), and reduce()—to manipulate the elements.

c.    Finally, use a terminal operation such as collect() or forEach() to yield the desired outcome.

 


 

Stream advantages

a.   Conciseness: Stream operations can  represent the complex data processing logic in just a few lines of code, unlike traditional loops.

b.   Immutability: Streams do not modify/alter the original data source, encouraging immutability and enhancing code safety.

c.    Parallelization: Stream operations have the potential to be executed in parallel, leading to better performance on processors with multiple cores.

 

Intermediate and terminal operations

In Java streams, operations can be categorized into two types: intermediate and terminal. Intermediate operations transform one stream into another, processing elements one by one in a lazy manner. These operations have no effect until the pipeline starts. On the other hand, terminal operations mark the end of the stream lifecycle and initiate the pipeline's execution. In a stream pipeline, composed of a source, zero or more intermediate operations, and a terminal operation, the computation is performed lazily, only when the terminal operation is invoked.

 

peek() method

peek() is a fundamental method offered by the Stream interface, that lets developers quickly check the elements of a stream without disrupting the flow of stream operations.

 

The peek() method's signature is as follows:

Stream<T> peek(Consumer<? super T> action)

 

It takes a Consumer functional interface as its argument, and executes an action on each element of the stream without modifying them. Logging the elements of a stream is one of its main use case for peek() method.

 

PeekDemo.java

package com.sample.app.streams;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class PeekDemo {

	public static void main(String args[]) {
		List<String> names = Arrays.asList("Thulasi", "Ram", "Hari", "Chamu", "Tataji");

		Stream<String> namesStartsWithTAndInUppercase = names.stream()
				.peek(ele -> System.out.println("Processing the element " + ele)).filter(name -> name.startsWith("T"))
				.peek(ele -> System.out.println("\tElement received after applying the filter " + ele))
				.map(name -> name.toUpperCase())
				.peek(ele -> System.out.println("\tElement received after applying the transformation " + ele));

		System.out.println("Streams is defined with intermittent operations");

		namesStartsWithTAndInUppercase.forEach(ele -> System.out.println("Final element : " + ele + "\n"));

	}

}

Output

Streams is defined with intermittent operations
Processing the element Thulasi
	Element received after applying the filter Thulasi
	Element received after applying the transformation THULASI
Final element : THULASI

Processing the element Ram
Processing the element Hari
Processing the element Chamu
Processing the element Tataji
	Element received after applying the filter Tataji
	Element received after applying the transformation TATAJI
Final element : TATAJI

Adding peek() before and after the filter and map operations helps us to see which elements are being worked on and how these stream operations affects the stream. This is really useful for fixing errors, especially when the logic in the stream operations gets complicated.


Previous                                                 Next                                                 Home

No comments:

Post a Comment