Java 8 Stream API: Functional Programming Made Easy

Beginner's Guide to Java Stream API


In my latest blog post, I delve into the intricacies of the Java Stream API, breaking down each concept and operation step-by-step with clear and comprehensive code examples. Whether you're new to functional programming or looking to enhance your understanding of streams in Java, this post aims to demystify the Java Stream API with detailed explanations and practical use cases. For a deeper dive into the code snippets referenced throughout the post, you can explore the full repository on my  👉 GitHub.

Getting Started

In Java, a stream is a sequence of elements that supports various operations to perform computations upon its elements. It is not a data structure itself. The Stream API was introduced in Java 8 to facilitate functional-style operations on streams of elements, such as collections (like lists and sets) or other sources. It allows developers to express common manipulations (like filtering, mapping, sorting, etc.) on these collections concisely and efficiently.

Creating Streams

                Streams can be created from various sources including:

              Collections

Use stream() method from collections like List, Set, Map, etc.

                                List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

                                Stream<Integer> stream = numbers.stream();

 

            Arrays

 Use Arrays.stream() for arrays of primitive types or Stream.of() for arrays of objects

                                int[] array = {1, 2, 3, 4, 5};

                                IntStream stream = Arrays.stream(array);

 

                Static Factory Methods  

Stream interface provides static factory methods like Stream.of(), Stream.iterate(),       Stream.generate(), etc.,

                                Stream<String> stringStream = Stream.of("a", "b", "c");

 

Data Processing with Collections   

01.EXAMPLE  👇

 This example demonstrates the basic usage of streams in Java for processing and iterating over elements in a collection.

    Explanation:

                     List Initialization:

List<Integer> nums = Arrays.asList(20, 10, 50, 30, 40);

                                This line creates a List of integers nums containing the elements [20, 10, 50, 30, 40]

                                using Arrays.asList().

                    Creating a Stream:

Stream<Integer> data = nums.stream();

                                stream() is a method available on the Collection interface (and its implementations like List). It converts the collection (nums in this case) into a sequential stream of elements (Integer in this case).

Processing the Stream:

data.forEach(n -> System.out.println(n));

                                forEach() is a terminal operation that iterates over each element of the stream and applies the provided function (n -> System.out.println(n)) to each element.

                                In this case, the lambda expression n -> System.out.println(n) prints each element (n) of the stream to the console.

02.EXAMPLE👇

   Explanation:

                                List Initialization:

List<Integer> nums = Arrays.asList(20, 10, 50, 30, 40);

                                Creates a list nums containing integers [20, 10, 50, 30, 40] using Arrays.asList().

                                Creating a Stream:

                                Stream<Integer> data = nums.stream();

                                Converts the list nums into a stream data of type Stream<Integer> using the stream() method provided by the List interface.

                               Processing the Stream:

                                data.forEach(n -> System.out.println(n));

                                Uses the forEach() method to iterate over each element (n) in the stream and print it to the console. This is a terminal operation that consumes the elements of the stream.

Trying to Reuse the Stream:

data.forEach(n -> System.out.println(n));

                Attempts to reuse the same stream data again to iterate and print its elements.

                                Issue:

 In Java streams, once a terminal operation (like forEach) is called on a stream, the stream is considered consumed and cannot be reused. This results in an IllegalStateException with the message "stream has already been operated upon or closed".

03.EXAMPLE👇

Explanation:

Imports:

java.util.Arrays: Provides utility methods for arrays, including asList used here.

java.util.List: Represents an ordered collection (list).

java.util.function.Consumer: Represents an operation that accepts a single input argument and returns no result.

java.util.stream.Stream: Represents a sequence of elements supporting sequential and parallel aggregate operations.

Class Declaration:

                                     public class DoubleTheDataUsingStream

 Defines a public class named DoubleTheDataUsingStream.

Main Method (public static void main(String[] args))

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 10, 50, 30, 40);

Creates a list named nums containing integers 20, 10, 50, 30, 40.

Stream Creation:

Stream<Integer> data = nums.stream();

Converts the nums list into a stream of integers (Stream<Integer>).

Stream Mapping:

Stream<Integer> mappedData = data.map(n -> n * 2);

Applies a transformation to each element in the data stream by doubling each integer (n * 2). This creates a new stream (mappedData) where each element is twice the corresponding element in data.

                         Stream Terminal Operation (forEach):

mappedData.forEach(n -> System.out.println(n));

Performs an action for each element of the mappedData stream. Here, it prints each element to the console.

Execution Flow:

The list nums is converted into a stream (data).

Each element in data is then mapped to its double value (mappedData).

Finally, each doubled value in mappedData is printed to the consol 

04.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import is not used in the code and can be removed without affecting the functionality.

Class Declaration:

public class DoubledTheValue: Defines a public class named DoubledTheValue.

Main Method (public static void main(String[] args)):This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 10, 50, 30, 40);

Creates a list named nums containing integers 20, 10, 50, 30, 40.

Enhanced For-loop (for (int n : nums)):

Iterates over each element (n) in the nums list.

Calculation and Output:

System.out.println(n * 2);

For each integer n in the list, multiplies it by 2 (n * 2) and prints the result to the console.

 05.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import statement is not used in the code and can be safely removed.

Class Declaration:

public class DoubleTheValueUsingBuilderPattern: Defines a public class named DoubleTheValueUsingBuilderPattern.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 10, 50, 30, 40);

Creates a list named nums containing integers 20, 10, 50, 30, 40.

Stream Processing:

nums.stream(): Converts the nums list into a stream of integers.

.map(n -> n * 2):  Applies a mapping function to each number in the stream to double its value (n * 2).

              .forEach(n -> System.out.println(n)): Iterates over each element in the stream and prints it to the console.

06.EXAMPLE👇


Explanation:

Imports:

java.io.LineNumberInputStream: This import statement is not used in the code and can be safely removed.

Class Declaration:

public class SortedDataList: Defines a public class named SortedDataList.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 10, 50, 30, 40);

Creates a list named nums containing integers 20, 10, 50, 30, 40.

Stream Creation:

Stream<Integer> data = nums.stream();

Converts the nums list into a stream of integers (Stream<Integer>).

Sorting:

Stream<Integer> sortedData = data.sorted();

Sorts the elements in the stream data. By default, it sorts in natural order (ascending).

Terminal Operation (forEach):

sortedData.forEach(n -> System.out.println(n));

Iterates over each element in the sorted.Data stream and prints it to the console.

07.EXAMPLE👇

Explanation:

         Imports:

            java.io.LineNumberInputStream: This import statement is not used in the code and can be safely removed.

Class Declaration:

public class Filter_Sort_MapTheValue: Defines a public class named Filter_Sort_MapTheValue.

Main Method (public static void main(String[] args)):This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 11, 50, 30, 43);

Creates a list named nums containing integers 20, 11, 50, 30, 43.

Stream Creation:

nums.stream(): Converts the nums list into a stream of integers.

Stream Operations:

.filter(n -> n % 2 == 1): Filters the stream to include only odd numbers (n % 2 == 1).

.sorted(): Sorts the filtered numbers in ascending order (natural order).

.map(n -> n * 2): Applies a mapping function to each number in the stream to double its value (n * 2).

.forEach(n -> System.out.println(n)): Iterates over each element in the stream and prints it to the console.

08.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import is not used in the code and can be removed.

Class Declaration:

public class PredicateFunction: Defines a public class named PredicateFunction.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 11, 50, 30, 43);

Creates a list named nums containing integers 20, 11, 50, 30, 43.

Predicate Definition:

Predicate<Integer> predi = new Predicate<Integer>() { ... };

Defines a predicate named predi that checks if a given integer is odd (n % 2 == 1). This is implemented using an anonymous inner class that overrides the test method of the Predicate interface.

Stream Processing:

nums.stream(): Converts the nums list into a stream of integers.

.filter(predi): Filters the stream to include only numbers that satisfy the predicate predi (i.e., odd numbers).

.sorted(): Sorts the filtered numbers in ascending order (natural order).

.map(n -> n * 2): Applies a mapping function to each number in the stream to double its value.

.forEach(n -> System.out.println(n)): Iterates over each element in the stream and prints it to the console.

09.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import is not used in the code and can be removed.

Class Declaration:

public class PredicateFunctionShort2: Defines a public class named PredicateFunctionShort2.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 11, 50, 30, 43);

Creates a list named nums containing integers 20, 11, 50, 30, 43.

Predicate Definition Using Lambda Expression:

Predicate<Integer> predi = n -> n % 2 == 1;

Defines a predicate named predi using a lambda expression. The lambda expression checks if a given integer n is odd (n % 2 == 1).

Stream Processing:

    nums.stream(): Converts the nums list into a stream of integers.

.filter(predi): Filters the stream to include only numbers that satisfy the predicate predi (i.e., odd numbers).

.sorted(): Sorts the filtered numbers in ascending order (natural order).

.map(n -> n * 2): Applies a mapping function to each number in the stream to double its value.

.forEach(n -> System.out.println(n)): Iterates over each element in the stream and prints it to the console.

10.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import is not used in the code and can be removed.

Class Declaration:

public class PredicateFunctionShort1: Defines a public class named PredicateFunctionShort1.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 11, 50, 30, 43);

Creates a list named nums containing integers 20, 11, 50, 30, 43.

Predicate Definition:

Predicate<Integer> predi = new Predicate<Integer>() { ... };

Defines a predicate named predi that checks if a given integer is odd (n % 2 == 1). This is implemented using an anonymous inner class that overrides the test method of the Predicate interface.

Stream Processing:

nums.stream(): Converts the nums list into a stream of integers.

.filter(predi): Filters the stream to include only numbers that satisfy the predicate predi (i.e., odd numbers).

.sorted(): Sorts the filtered numbers in ascending order (natural order).

.map(n -> n * 2): Applies a mapping function to each number in the stream to double its value.

.forEach(n -> System.out.println(n)): Iterates over each element in the stream and prints it to the console.

11.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import is not used in the code and can be removed.

Class Declaration:

public class PredicateFunctionShort2: Defines a public class named PredicateFunctionShort2.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 11, 50, 30, 43);

Creates a list named nums containing integers 20, 11, 50, 30, 43.

Predicate Definition Using Lambda Expression:

Predicate<Integer> predi = n -> n % 2 == 1;

Defines a predicate named predi using a lambda expression. The lambda expression checks if a given integer n is odd (n % 2 == 1).

Stream Processing:

nums.stream(): Converts the nums list into a stream of integers.

.filter(predi): Filters the stream to include only numbers that satisfy the predicate predi (i.e., odd numbers).

.sorted(): Sorts the filtered numbers in ascending order (natural order).

.map(n -> n * 2): Applies a mapping function to each number in the stream to double its value.

.forEach(n -> System.out.println(n)): Iterates over each element in the stream and prints it to the console.

  12.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import statement is not used in the code and can be safely removed.

Class Declaration:

public class PredicateFunctionShortestCode: Defines a public class named PredicateFunctionShortestCode.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 11, 50, 30, 43);

Creates a list named nums containing integers 20, 11, 50, 30, 43.

Stream Creation:

nums.stream(): Converts the nums list into a stream of integers.

Stream Operations:

.filter(n -> n % 2 == 1): Filters the stream to include only odd numbers (n % 2 == 1).

.sorted(): Sorts the filtered numbers in ascending order (natural order).

.map(n -> n * 2): Applies a mapping function to each number in the stream to double its value (n * 2).

.forEach(n -> System.out.println(n)): Iterates over each element in the stream and prints it to the console. 

12.EXAMPLE👇

Explanation:

Imports:

java.io.LineNumberInputStream: This import statement is not used in the code and can be safely removed.

Class Declaration:

public class AddingValueInTheList: Defines a public class named AddingValueInTheList.

Main Method (public static void main(String[] args)):

This is the entry point of the Java program.

List Initialization:

List<Integer> nums = Arrays.asList(20, 11, 50, 30, 43);

Creates a list named nums containing integers 20, 11, 50, 30, 43.

Stream Creation:

nums.stream(): Converts the nums list into a stream of integers.

Stream Operations:

.filter(n -> n % 2 == 1): Filters the stream to include only odd numbers (n % 2 == 1).

.sorted(): Sorts the filtered numbers in ascending order (natural order).

.map(n -> n * 2): Applies a mapping function to each number in the stream to double its value (n * 2).

.reduce(0, (CarriedValue, NewValue) -> CarriedValue + NewValue): Reduces the stream to a single value by summing all elements, starting with an initial value of 0.

Result Assignment and Output:

int result = ...: Stores the result of the stream operations (sum of doubled odd numbers).

System.out.println(result);: Prints the computed sum to the console.

 Key Characteristics of Streams

Sequence of Elements: A stream provides a way to convey elements from a source, one at a time, through a sequence of operations.

Aggregate Operations: Streams support aggregate operations like filtering, mapping, sorting, and reducing elements. These operations can be chained together to form a pipeline.

Lazy Evaluation: Intermediate operations on a stream are lazily evaluated. This means that intermediate operations do not start processing elements until a terminal operation is invoked. Lazy evaluation allows streams to optimize performance by avoiding unnecessary computations.

Immutability: Streams do not modify the underlying data source. Instead, they provide an interface to perform operations on the data in a non-destructive manner.

Parallel Execution: Streams can leverage parallelism to enhance performance for large datasets. Parallel streams automatically distribute the workload across multiple threads to process elements concurrently, if the underlying collection or ope

Comments

Popular posts from this blog

Unveiling the Trinity of Standards, Objects, and Classes

Getting Started with Design Patterns - Part 1

A Guide to Object Generating, Data Types, Parameters and Arguments