Java Stream API Explained with Examples

Introduction

The API (Application Programming Interface) refers to a vast collection of pre-written Java packages such as java.lang, java.util, java.io, java.sql, etc.  It also includes classes and interfaces located in the java.util.stream package where interfaces are used to define the stream pipeline operations along with a few supporting classes and also used to handle specific data types. The Stream API benefits developers by providing a functional, declarative and efficient approach to data processing. It allows developers to process collections of data in a declarative and efficient way.

In this article,we’ll learn about the Java Stream API with a programming part involved in this topic. We will understand how streams are created from different data sources, how intermediate and terminal operations form a stream pipeline, and how lambda expressions and functional interfaces play a key role in stream processing.

What is Stream API in Java and how are they created?

A stream acts like a pipeline through which data flows, supporting operations such as filter for selecting the elements that match the given condition, map to transforms each element of the stream by applying a function, and many more.

A stream is not a data structure. It does not store elements, instead it processes data from sources like collections, arrays, or input/output channel. Given below are the key characteristics of the stream API in Java.

  • No Storage: Streams do not store the data themselves like list, set or array. Instead, it simply processes data from an existing source.
  • Functional in Nature: Stream API follows the principles of functional programming where behavior (logic) is treated as data and passed around as arguments. Operations are applied using lambda expressions or method references.
  • Lazy Evaluation:It means that intermediate operations such as filter(), map(), sorted(), and distinct() are not executed immediately when they are defined. Instead, they are stored as part of a pipeline and are executed only when a terminal operation such as collect(), forEach(), count() is invoked.  
  • Non-Mutability: It means that stream operations do not change or modify the original data source. Streams operate on the data and produce a new result without altering the underlying collection.
  • Pipelining: It is the most powerful feature in Java Stream API that allows multiple stream operations to be connected together so that data flows in a sequential way. This chain of operations is referred to as stream pipeline.

How are Streams Created?

Streams are created from different data sources such as Java lists, Java sets, and Java arrays. Given below is the list of creating streams from collections, arrays, file input, and static factory methods.

  1. From Collections: One of the most common and practical ways to create a stream in Java is from a collection using the stream() method, or a parallel stream using parallelStream().

Java

List<String> names = Arrays.asList(“Java”, “Python”, “JavaScript”);

Stream<String> namesStream = names.stream();

  1. From Arrays: Streams are created directly from array using Arrays.stream() and Stream.of(). It is very commonly used when you want to process array elements using the Stream API.Given below is an example of creating a stream from an array.

Java

String[] namesOfStudents = {“Prathiba”, “Sonali”, “Rohit”, “Gaurav”};

Stream<String> strStream = Arrays.stream(namesofStudents);

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

  1. From File I/O: Streams are created from files input using Files.lines(Path path) which allows you to read a file line by line and process it using the Stream API. This method does not load the entire file into memory and reads the lazy files. Given below is the implementation of creating a stream from File I/O.

Java

try (Stream<String> lines = Files.lines(Paths.get(“data.text”))) {

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

} catch (IOException e) {

    e.printStackTrace();

}

How intermediate and terminal operations form a Stream pipeline?

A Stream pipeline is formed in Java when a stream source is connected with one or more intermediate operations followed by a terminal operation.

An intermediate operation in Java Stream are intermediate step in a pipeline that transforms data to return a new stream that allows further chaining. Each intermediate operation creates a new stream, enabling a chain of operations. Common examples are filter(), map(), flatMap(), distinct(), sorted(), limit(), skip().

A terminal operation in the Java stream API documentation is the final step in a stream pipeline that initiates the processing of the data and produces a result. Common examples in terminal operation include collect(), count(), reduce(), toArray(), etc.

Given below is the flow chart of the stream pipeline

Source -> Intermediate Operations -> Terminal Operation

How lambda expressions and functional interfaces play a key role in Stream Processing?

A lambda expression is a short, concise way to implement a functional interfaces, which is an interface that contains exactly one abstract method.An abstract method is a method that is declared without a body. Lambda expressions provide a concise and expressive way to define the behavior for stream operations like filtering, mapping, and reduction.

Given below is the list of points illustrating the importance of lambda expressions in Java Stream.

  1. Enabling Declarative Data Processing: Lambda expressions allow you to tell the Stream API what to do with the elements instead of telling the computer how to iterate through a collection. Given below is an example of iterating over a collection through a lambda expression.

Java

names.stream()

            .filter(name -> name.startsWith(“A”))

            .forEach(System.out::println);

  1. Conciseness and Readability: Lambda expressions eliminate the boilerplate code associated with anonymous inner classes. This makes the code shorter, easier to read, and allows the code logic to stand out. For example, implementing Predicate<T> or Function<t, R>, which removes class declaration, removes method override, and keeps only the logic.
  1. Implementation of Functional Interfaces: Streams depend on built-in functional interfaces because they provide the mechanism to pass behavior (code) as arguments to stream methods. Given below is the table describing the use of each interface in Java stream.
InterfaceMethodUsed InPurpose
Predicate<T>boolean test(T t)filter()Condition checking
Function<T,R>R apply(T t)map()Transformation
Consumer<T>void accept(T t)forEach()Perform action
Supplier<T>T get()Stream.generate()Generate values
  1. Facilitating Parallel Processing: Lambda expression switches from sequential to parallel processing by changing .stream() to .parallelStream(). The key reason lambdas make parallel processing easy because they allow behavior to be passed as independent functions and work independently for each element.

Author: 99 Tech Post

99Techpost is a leading digital transformation and marketing blog where we share insightful contents about Technology, Blogging, WordPress, Digital transformation and Digital marketing. If you are ready digitize your business then we can help you to grow your business online. You can also follow us on facebook & twitter.

Leave a Comment