Showing posts with label streams. Show all posts
Showing posts with label streams. Show all posts

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

Wednesday, 24 April 2024

Understanding Intermediate and Terminal Operations in Stream Processing

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.

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.

 

HelloWorld.java

package com.sample.app.streams;

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

public class HelloWorld {

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

		List<String> namesStartsWithTAndInUppercase = names.stream().filter(name -> name.startsWith("T"))
				.map(name -> name.toUpperCase()).collect(Collectors.toList());

		namesStartsWithTAndInUppercase.forEach(System.out::println);

	}

}

 

Output

THULASI
TATAJI

 

List<String> namesStartsWithTAndInUppercase = names
    .stream()
    .filter(name -> name.startsWith("T"))
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());

 

Above Java code snippet demonstrates the usage of Java streams to filter and transform a list of names.

 

Let's break it down.

a.   stream(): This is a terminal operation that converts the names list into a stream. It marks the beginning of the stream pipeline.

b.   filter(): This is an intermediate operation that filters the stream based on the given predicate. In this case, it filters names that start with the letter "T".

c.    map(): This is another intermediate operation that transforms each element of the stream. Here, it converts each name to uppercase using the toUpperCase() method.

d.   collect(Collectors.toList()): This is a terminal operation that collects the elements of the stream into a list. It consumes the stream and produces a new list containing the filtered and transformed elements.

So, in summary:

 

a.   Intermediate operations: filter() and map()

b.   Terminal operation: collect(Collectors.toList())

 

Intermediate operations will not get triggered until the terminal operation is initiated

 

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

In this Java code snippet, a stream pipeline is defined for processing a list of names. The pipeline includes intermediate operations such as peek() to log information about each element as it passes through the stream, filter() to select only names starting with "T", and map() to convert each name to uppercase. However, despite defining these intermediate operations, no processing occurs until a terminal operation is invoked. Only when forEach() is called to print the final elements do the intermediate operations get triggered, executing the pipeline and producing the desired output. This highlights the lazy evaluation nature of Java streams, where intermediate operations are deferred until a terminal operation initiates the stream processing.

 

Previous                                                 Next                                                 Home

Thursday, 18 May 2023

Construct a map from array of objects using streams

Below snippet get the map from employees array, where key is the employee id and object is employee itself.

Map<Integer, Employee> empById = unmodifiableMap(Arrays.stream(emps).collect(Collectors.toMap(Employee::getId, identity())));

Find the below working application.

 

MapFromArray.java

package com.sample.app.collections;

import static java.util.Collections.unmodifiableMap;
import static java.util.function.Function.identity;

import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class MapFromArray {

    private static class Employee {
        private int id;
        private String name;

        public Employee(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() {
            return id;
        }

        @Override
        public String toString() {
            return "Employee [id=" + id + ", name=" + name + "]";
        }

    }

    public static void main(String[] args) {
        Employee emp1 = new Employee(1, "Krishna");
        Employee emp2 = new Employee(2, "Krishna");
        Employee emp3 = new Employee(3, "Krishna");
        Employee emp4 = new Employee(4, "Krishna");

        Employee[] emps = { emp1, emp2, emp3, emp4 };

        Map<Integer, Employee> empById = unmodifiableMap(
                Arrays.stream(emps).collect(Collectors.toMap(Employee::getId, identity())));

        for (Entry<Integer, Employee> entry : empById.entrySet()) {
            System.out.println(entry.getKey() + "\t" + entry.getValue());
        }
    }

}

Output

1   Employee [id=1, name=Krishna]
2   Employee [id=2, name=Krishna]
3   Employee [id=3, name=Krishna]
4   Employee [id=4, name=Krishna]



 

You may like

Interview Questions

Collection programs in Java

Array programs in Java

Get the stream from Enumeration in Java

LinkedHashTable implementation in Java

Get the enumeration from a Collection

Get the enumeration from an Iterator in Java

Get a map from enum in Java

Tuesday, 9 May 2023

Get the enumeration from a Collection

Using Collections.enumeration method, we can get an enumeration over the specified collection.

 

Signature

public static <T> Enumeration<T> enumeration(final Collection<T> c)

Example

Enumeration<Integer> enumeration = Collections.enumeration(primesCollection);

Find the below working application.

 

EnumerationFromCollectionDemo.java

package com.sample.app.collections;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;

public class EnumerationFromCollectionDemo {

	public static void main(String[] args) {
		Collection<Integer> primesCollection = Arrays.asList(2, 3, 5, 7, 11);

		Enumeration<Integer> enumeration = Collections.enumeration(primesCollection);
		while (enumeration.hasMoreElements()) {
			System.out.println(enumeration.nextElement());
		}
	}

}

Output

2
3
5
7
11


You may like

Interview Questions

Collection programs in Java

Array programs in Java

Get a composite unmodifiable array list from two arrays

Join the collection elements by a separator using streams

Get the stream from an iterator in Java

Get the stream from Enumeration in Java

LinkedHashTable implementation in Java

Monday, 8 May 2023

Introduction to Spliterator interface in Java

Spliterator instance is used to traverse and partition the elements of a source. Array, Collection, IOChannel etc., come under spliterator source.

 

Example 1: Traverse the List elements using spliterator.

List<Integer> primesList = Arrays.asList(2, 3, 5, 7, 11);
primesList.spliterator().forEachRemaining(System.out::println);

Example 2: Partition the elements of spliterator.

'trySplit' method splits the current spliterator’s elements into two parts if possible and returns a new Spliterator object covering some portion of the elements, or null if this spliterator cannot be split.

 

SpliteratorPartition.java

package com.sample.app;

import java.util.Arrays;
import java.util.Spliterator;

public class SpliteratorPartition {

	public static void main(String[] args) {

		Spliterator<Integer> spliterator1 = Arrays.asList(2, 3, 5, 7, 11, 13).spliterator();
		Spliterator<Integer> spliterator2 = spliterator1.trySplit();

		System.out.println("Elements in spliterator 1");
		spliterator1.forEachRemaining(System.out::println);

		System.out.println("\nElements in spliterator 2");
		spliterator2.forEachRemaining(System.out::println);
	}

}

Output

Elements in spliterator 1
7
11
13

Elements in spliterator 2
2
3
5

As you see above output, spliterator1 is initially hold 6 elements (2, 3, 5, 7, 11, 13). After the split, spliterator1 holds (7, 11, 13) and spliterator 2 holds (2, 3, 5).

 

Spliterator traversal

You can traverse the spliterator in two ways.

a. Traverse individually using tryAdvance method

b. Traverse sequentially in bulk using forEachRemaining method

 

Traverse individually using tryAdvance method

while (spliterator1.tryAdvance(element -> System.out.println(element))) {}

 

Find the below working application.

 

SpliteratorTraverseOneByOne.java

package com.sample.app;

import java.util.Arrays;
import java.util.Spliterator;

public class SpliteratorTraverseOneByOne {

	public static void main(String[] args) {

		Spliterator<Integer> spliterator1 = Arrays.asList(2, 3, 5, 7, 11, 13).spliterator();

		while (spliterator1.tryAdvance(element -> System.out.println(element))) {
			
		}
			
	}

}

Output

2
3
5
7
11
13

Traverse sequentially in bulk using forEachRemaining method

Example

spliterator1.forEachRemaining(System.out::println);

SpliteratorTraverseInBulk.java

package com.sample.app;

import java.util.Arrays;
import java.util.Spliterator;

public class SpliteratorTraverseInBulk {
	
	public static void main(String[] args) {

		Spliterator<Integer> spliterator1 = Arrays.asList(2, 3, 5, 7, 11, 13).spliterator();

		spliterator1.forEachRemaining(System.out::println);

	}
}

Output

2
3
5
7
11
13

Split the data using trySplit method

'trySplit' method splits the current spliterator’s elements into two parts if possible and returns a new Spliterator object covering some portion of the elements, or null if this spliterator cannot be split. You can depict the same from below figure.




Find the below working application.

 

SpliteratorTrySplit.java


package com.sample.app;

import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;

public class SpliteratorTrySplit {

	private static List<Integer> nElements(int noOfElements) {
		List<Integer> result = new ArrayList<>();

		for (int i = 1; i < noOfElements + 1; i++) {
			result.add(i);
		}

		return result;

	}

	private static void printElements(Spliterator<?> spliterator, String message) {
		System.out.println(message);
		spliterator.forEachRemaining(ele -> System.out.print(ele + " "));
		System.out.println();
	}

	public static void main(String[] args) {
		List<Integer> list1 = nElements(16);

		Spliterator<Integer> spliterator1 = list1.spliterator();
		printElements(spliterator1, "spliterator1 elements");

		System.out.println("\nSplit the spliterator once");
		spliterator1 = list1.spliterator();
		Spliterator<Integer> spliterator2 = spliterator1.trySplit();
		printElements(spliterator1, "spliterator1 elements");
		printElements(spliterator2, "spliterator2 elements");

		System.out.println("\nSplit the spliterator1 twice and spliterator2 twice");
		spliterator1 = list1.spliterator();
		spliterator2 = spliterator1.trySplit();
		Spliterator<Integer> spliterator3 = spliterator1.trySplit();
		Spliterator<Integer> spliterator4 = spliterator2.trySplit();
		printElements(spliterator1, "spliterator1 elements");
		printElements(spliterator2, "spliterator2 elements");
		printElements(spliterator3, "spliterator3 elements");
		printElements(spliterator4, "spliterator4 elements");

	}

}

Output

spliterator1 elements
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 

Split the spliterator once
spliterator1 elements
9 10 11 12 13 14 15 16 
spliterator2 elements
1 2 3 4 5 6 7 8 

Split the spliterator1 twice and spliterator2 twice
spliterator1 elements
13 14 15 16 
spliterator2 elements
5 6 7 8 
spliterator3 elements
9 10 11 12 
spliterator4 elements
1 2 3 4

Spliterator Characteristics

A Spliterator reports a set of characteristics of its structure, source, and elements. Following table summarizes the characteristics of the spliterator.

 

Characteristic

Description

ORDERED

Iterate over the elements in order.

DISTINCT

For each pair of encountered elements x, y, !x.equals(y). This applies for example, to a Spliterator based on a Set.

SORTED

Characteristic value signifying that encounter order follows a defined sort order.

SIZED

Characteristic value signifying that the value returned from estimateSize() prior to traversal or splitting represents a finite size that, in the absence of structural source modification, represents an exact count of the number of elements that would be encountered by a complete traversal.

NONNULL

Characteristic value signifying that the source guarantees that encountered elements will not be null. (This applies, for example, to most concurrent collections, queues, and maps.)

IMMUTABLE

Characteristic value signifying that the element source cannot be structurally modified; that is, elements cannot be added, replaced, or removed, so such changes cannot occur during traversal. A Spliterator that does not report IMMUTABLE or CONCURRENT is expected to have a documented policy (for example throwing ConcurrentModificationException) concerning structural interference detected during traversal.

CONCURRENT

Characteristic value signifying that the element source may be safely concurrently modified (allowing additions, replacements, and/or removals) by multiple threads without external synchronization. If so, the Spliterator is expected to have a documented policy concerning the impact of modifications during traversal.

SUBSIZED

Characteristic value signifying that all Spliterators resulting from trySplit() will be both SIZED and SUBSIZED. This means that all child Spliterators, whether direct or indirect, will be SIZED.

 

 

Find the below working application to get the characteristics of a spliterator.

 

SpliteratorCharacterstics.java

package com.sample.app.streams;

import java.util.Arrays;
import java.util.List;
import java.util.Spliterator;

public class SpliteratorCharacterstics {

	public static void main(String[] args) {
		List<Integer> primesList = Arrays.asList(2, 3, 5, 7, 11);
		Spliterator<Integer> spliterator = primesList.spliterator();

		System.out.println("is ORDERED ? " + spliterator.hasCharacteristics(Spliterator.ORDERED));
		System.out.println("is DISTINCT ? " + spliterator.hasCharacteristics(Spliterator.DISTINCT));
		System.out.println("is SORTED ? " + spliterator.hasCharacteristics(Spliterator.SORTED));
		System.out.println("is SIZED ? " + spliterator.hasCharacteristics(Spliterator.SIZED));
		System.out.println("is NONNULL ? " + spliterator.hasCharacteristics(Spliterator.NONNULL));
		System.out.println("is IMMUTABLE ? " + spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
		System.out.println("is CONCURRENT ? " + spliterator.hasCharacteristics(Spliterator.CONCURRENT));
		System.out.println("is SUBSIZED ? " + spliterator.hasCharacteristics(Spliterator.SUBSIZED));

	}

}

Output

is ORDERED ? true
is DISTINCT ? false
is SORTED ? false
is SIZED ? true
is NONNULL ? false
is IMMUTABLE ? false
is CONCURRENT ? false
is SUBSIZED ? true


 

Previous                                                 Next                                                 Home