0% found this document useful (0 votes)
37 views14 pages

Stream API: Intermediate Operations Guide

The document provides an overview of intermediate operations in the Java Stream API, including methods like filter(), map(), flatMap(), distinct(), and sorted(). Each operation is described with its purpose, syntax, and practical examples, emphasizing their lazy evaluation, chainability, and how they transform streams. Real-world use cases and comparisons to SQL operations are also included to illustrate the functionality of these methods.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
37 views14 pages

Stream API: Intermediate Operations Guide

The document provides an overview of intermediate operations in the Java Stream API, including methods like filter(), map(), flatMap(), distinct(), and sorted(). Each operation is described with its purpose, syntax, and practical examples, emphasizing their lazy evaluation, chainability, and how they transform streams. Real-world use cases and comparisons to SQL operations are also included to illustrate the functionality of these methods.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Stream API

End-to-End with Intermediate Operations

IP
ND
SA

​ ​ ​ ​ Notes By Sandip Vargale


Intermediate Operation
🔹 An intermediate operation is a method in the Java Stream API that transforms a
stream into another stream. It does not trigger processing of the data
immediately—instead, it builds up a pipeline of operations that will run later
when a terminal operation (like collect(), forEach(), etc.) is invoked..

🔹 Key Characteristics:
●​ Lazy Evaluation: Intermediate operations don’t do anything until a terminal
operation is called.
●​ Chainable: You can link multiple intermediate operations together (fluent
style).
●​ Return a Stream: They always return a new stream, allowing further operations.

Operations
🔹 [Link]()
✦ Purpose: Filters elements based on a predicate or given condition.​

IP
✦ Syntax: Stream<T> filter(Predicate<? super T> predicate)​

🔸 Example_1:

ND
List<String> names = [Link]("John", "Jane", "Tom", "Jake");
​ ​ [Link]()
​ ​ .filter(name -> [Link]("J"))
​ ​ .forEach([Link]::println);

🔸 Example_2:​
SA

public class StreamExample {


​ ​ public static void main(String[] args) {
​ ​ ​ List<Integer> numbers = [Link](10, 15, 20, 25, 30);
​ ​ ​ List<Integer> evenNumbers = [Link]()
​ ​ ​ ​ ​ ​ ​ .filter(n -> n % 2 == 0)
​ ​ ​ ​ ​ ​ ​ .collect([Link]());
​ ​ ​ [Link](evenNumbers); // Output: [10, 20, 30]
​ ​ }
​ }

🔸 Example_3:

List<Integer> result = [Link](1, 3, 6, 8, 2, 5)
.filter(n -> n > 5)
.toList();
[Link](result); // Output: [6, 8]​

​ ​ ​ ​ Notes By Sandip Vargale


🧠 Real-time Use:
●​ Filtering out users whose names start with "J.
●​ Filtering out employees earning above a certain salary.

🔹 [Link]()
​ ​ ​ ​ ​

✦ Purpose:
●​ The map() method is an intermediate operation in the Java Stream [Link] is
used to transform (or map) each element of the stream from type T to
another type R.
●​ It returns a new stream consisting of the results of applying the given
Function to the elements of the original stream.

✦ Syntax: <R> Stream<R> map(Function<? super T, ? extends R> mapper)

► Breakdown of the Signature:
●​ <R> – This denotes a generic type of the result. It means that after

IP
mapping, each element in the new stream will be of type R.
●​ Function<? super T, ? extends R> – This is the functional interface used
to perform the transformation:
●​ T is the input type (type of elements in the original stream).
●​ R is the output type (type of elements in the resulting stream).
ND
●​ ? super T allows the function to accept T or any of its supertypes.
●​ ? extends R allows the result to be a subtype of R.

🔸 Example_1: Convert Strings to Uppercase
​ List<String> names = [Link]("alice", "bob", "charlie");
SA

​ List<String> upperNames = [Link]()


​ ​ .map(String::toUpperCase) // Function<String, String>
​ ​ .toList();

​ [Link](upperNames); // [ALICE, BOB, CHARLIE]

► Note:
●​ String::toUpperCase is a function that takes a String and returns a
String.
●​ Here, T = String, R = String.

🔸 Example_2: Convert List of Strings to List of Lengths (Integer)
​ ​ List<String> names = [Link]("apple", "banana", "kiwi");
​ ​
​ ​ List<Integer> nameLengths = [Link]()
​ ​ ​ .map(String::length) // Function<String, Integer>
​ ​ ​ .toList();
​ ​

​ ​ ​ ​ Notes By Sandip Vargale


​ ​ [Link](nameLengths); // [5, 6, 4]

► Note:
●​ This converts each String to its length.
●​ Here, T = String, R = Integer.

🔸 Example_3: Convert List of Objects to List of Fields

public class Employee {
String name;
int age;
public Employee(String name, int age) {
[Link] = name;
[Link] = age;
}
public String getName() { return name; }
}

IP
}

List<Employee> employees = [Link](
​ ​ new Employee("Alice", 30),
ND
​ ​ new Employee("Bob", 25)
);

List<String> employeeNames =
[Link]()
​ ​ .map(Employee::getName) // Function<Employee, String>
​ ​ .collect([Link]());
SA


[Link](employeeNames); // [Alice, Bob]

🧠 Real-World Use Cases
●​ Transforming data before saving it to the database.
●​ Extracting specific fields from complex objects.
●​ Converting one data structure into another (e.g., DTO mapping).

🔹 [Link]()
✦ Purpose: Flattens nested structures/stream into a single stream.
​ flatMap() is a method in Java's Stream API that:
●​ Transforms each element in the stream into another stream (or collection).
●​ Then flattens all those nested streams into a single stream.
●​ In simple terms: map + flatten = flatMap

​ ​ ​ ​ Notes By Sandip Vargale


✦ Syntax:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> m);
●​ Applies this function to each element of the original stream (type T),
●​ Gets multiple streams of R,
●​ Flattens those into one single stream of R.

🔸 Explanation
●​ For each element T in the original stream, apply a function that returns a
stream of R, then flatten all those streams into one stream of R."

🧠 Real-time Example:
●​ Extracting all subjects from multiple student records.
●​ Reading nested lists from JSON files and flattening them.
●​ Merging multiple lists of user roles into a single list.
​ ​ ​ ​ ​

🎯 Real-World Analogy (SQL vs flatMap)

IP
●​ Imagine a database with two tables: like having RelationShip (OneToMany)
One Departments can have multiple Employees ()or Many Employees belongs to
ND
One Department
​ ​ i.e
🗃️ Departments Tables 🗃️Employees Tables
id name id name dept_id

1 IT 1 Ram 1
SA

2 HR 2 Shyam 1

3 Seeta 2

​ ​
​ ​
🎯 SQL Query (Join with Flattened Result):
●​ SELECT [Link] AS department, [Link] AS employee
FROM Departments d
JOIN Employees e ON [Link] = e.dept_id;
​ ​
●​ This query gives:

department employee

IT Ram

IT Ram

HR Seeta

​ ​ ​ ​ Notes By Sandip Vargale



► Note:
You joined departments with their employees and flattened all employee
lists into one single result set.

🔸 Java Equivalent Using flatMap


class Department {
​ String name;
​ List<Employee> employees;
​ // constructor, getter
}
​ ​
​ class Employee {
​ ​ String name;
​ ​ // constructor, getter
​ }

IP
🔸 Stream + flatMap Example:
​ ​
​ ​ List<Department> departments =
ND
new ArrayList<>([Link](
​ ​ new Department("IT",
new ArrayList<>([Link](new Employee("Ram"),
new Employee("Shyam")))),
​ ​ new Department("HR", new ArrayList<>([Link](
new Employee("Seeta"))))
​ ​ ));
SA

​ ​
​ ​ [Link]()
​ ​ ​ .flatMap(dept -> [Link]().stream())//flatten employee list
​ ​ ​ .map(Employee::getName) // get names only
​ ​ ​ .forEach([Link]::println);
🧾 Output:
Ram
Shyam
Seeta

🎨 Visual:
●​ Before flatMap: [ [Ram, Shyam],[Seeta] ]
●​ After flatMap: => [Ram, Shyam, Seeta]
​ ​
●​ Here, each department has its own list of employees (just like a subquery or
subtable).
●​ flatMap collects all these sublists into a single flattened stream — similar to
how SQL JOIN combines rows.

​ ​ ​ ​ Notes By Sandip Vargale
🧠 In Summary:
Concept SQL Java (Stream API)

Table Table / Row Object / Collection

Nested Data Subquery / JOIN List inside List (e.g.,


List<List<T>>)

Flattening Data JOIN or Subquery Result flatMap()

Transformation SELECT column map()

Combined SELECT + JOIN flatMap().map()

​ ​
► When to Use flatMap?
You have a stream of collections (e.g., List<List<String>>).
You want to combine them into a single stream before processing.

🔸 Example 1:

IP
List<List<String>> listOfLists = [Link](
​ [Link]("apple", "banana"),
ND
​ ​ [Link]("cherry", "date")
​ );
​ List<String> allItems = [Link]()
​ ​ ​ ​ ​ .flatMap(List::stream)
​ ​ ​ ​ ​ .toList();
​ [Link](allItems); // Output: [apple, banana, cherry, date]

🔸 Example 2:
SA

​ ​ List<List<String>> data = [Link](


​ ​ ​ [Link]("a", "b"),
​ ​ ​ [Link]("c", "d")
​ ​ );

​ ​ [Link]()
​ ​ ​ .flatMap(Collection::stream)
​ ​ ​ .forEach([Link]::println);​ ​
🔸 Example 3:

​ ​ List<String> sentences = [Link]("hello world", "java streams");

​ ​ List<String> words = [Link]()


​ ​ ​ .flatMap(sentence -> [Link]([Link](" ")))
​ ​ ​ .collect([Link]());
​ ​
​ ​ [Link](words); [hello, world, java, streams]

​ ​ ​ ​ Notes By Sandip Vargale


🔹 [Link]()
✦ Purpose: Removes duplicate elements ,distinct() works based on equals() and
hashCode() methods of your objects
✦ Syntax: Stream<T> distinct()

🔸 Example 1: Remove Duplicate Students

​ public class Student {
​ ​ private int id;
​ ​ private String name;
​ ​ // Constructor, Getters, Setters

​ ​ @Override
​ ​ public boolean equals(Object o) {
​ ​ ​ if (this == o) return true;
​ ​ ​ if (o == null || getClass() != [Link]()) return false;

IP
​ ​ ​ Student student = (Student) o;
​ ​ ​ return id == [Link] && [Link](name, [Link]);
​ ​ }

​ ​ @Override
ND
​ ​ public int hashCode() {
​ ​ ​ return [Link](id, name);
​ ​ }
​ }
​ List<Student> students = [Link](
​​ ​ new Student(1, "Alice"),
​​ ​ new Student(2, "Bob"),
SA

​​ ​ new Student(1, "Alice") // duplicate


​ );

​ List<Student> uniqueStudents = [Link]()
​ ​ ​ ​ ​ ​ .distinct()
​ ​ ​ ​ ​ ​ .toList();
​ [Link](s -> [Link]([Link]()));

🔸 Example 2 : 💡 Alternative (Custom Uniqueness)
​ If you want to define "duplicate" based only on one field (like id), use a
custom collector or filter like:

List<Student> uniqueById =
[Link]()
.collect([Link](
[Link](Student::getId, s -> s, (s1, s2) -> s1),
m -> new ArrayList<>([Link]())
));

​ ​ ​ ​ Notes By Sandip Vargale



​ This removes duplicates based only on the id.

🔸 Example 3 :
​ public class StreamExample {
​ ​ public static void main(String[] args) {
​ ​ ​ List<Integer> numbers = [Link](1, 2, 2, 3, 4, 4, 5);
​ ​ ​ List<Integer> uniqueNumbers =
[Link]()
​ ​ ​ ​ ​ .distinct()​​ ​ ​ ​ ​ ​
​ ​ .collect([Link]());
​ ​ ​ [Link](uniqueNumbers); // Output: [1, 2, 3, 4, 5]
​ ​ }
​ }

🧠 Real-World Use Case: Removing duplicate email addresses from a list.
🔹 [Link]()
IP
✦ Purpose: Sorts the elements in natural order or using a comparator.
✦ Syntax:
ND
●​ Stream<T> sorted(); // Default Natural Sorting
●​ Stream<T> sorted(Comparator<? super T> comparator); // Customise Sorting
​ ​ ​
► Note : This sorts elements in natural order, which means:
●​ For numbers: ascending order (e.g., 1, 2, 3)
●​ For strings: lexicographic order (e.g., "apple", "banana", "zebra")
●​ For custom objects: the class must implement Comparable<T>
SA


🔸 Example 1 with integers:
​ ​ List<Integer> numbers = [Link](5, 1, 3);

​ ​ List<Integer> sorted = [Link]()
​ ​ ​ .sorted()
​ ​ ​ .collect([Link]());
​ ​
​ ​ [Link](sorted); // Output: [1, 3, 5]
​ ​
🔸 Example 2 : Stream<T> sorted(Comparator<? super T> comparator)
​ ​ This allows you to define a custom sort order using a Comparator.

​ ​ List<Student> students = [Link](
​ ​ new Student(1, "Bob"),
​ ​ new Student(2, "Alice"),
​ ​ new Student(3, "Charlie")
​ ​ );

​ ​ ​ ​ Notes By Sandip Vargale


​ ​
​ ​ List<Student> sortedByName = [Link]()
​ ​ ​ .sorted([Link](Student::getName))
​ ​ ​ .collect([Link]());
​ ​
​ ​ [Link](s -> [Link]([Link]()));
​ ​
🔸 Example 3: Custom Sorting of Students

​ ​ public class Student {
​ ​ private int id;
​ ​ private String name;
​ ​ private double gpa;

​ ​ // Constructor, Getters, Setters
​ ​ }
​ ​
🔸 Example 4:

Code with Collectors and custom sort:

IP
Sort students by GPA (highest first), then by name (A–Z)
ND
​ ​ List<Student> students = [Link](
​ ​ new Student(1, "Alice", 3.5),
​ ​ new Student(2, "Bob", 3.9),
​ ​ new Student(3, "Charlie", 3.9),
​ ​ new Student(4, "David", 3.2)
​ ​ );
​ ​
SA

​ ​ List<Student> sorted = [Link]()


​ ​ ​ .sorted(
​ ​ ​ [Link](Student::getGpa).reversed()
​ ​ ​ ​ ​ ​ .thenComparing(Student::getName)
​ ​ ​ )
​ ​ ​ .toList();
​ ​
​ ​ [Link](s -> [Link]([Link]() + " - " + [Link]()));

🧾 Output:
​ ​ ​ Bob - 3.9
​ ​ ​ Charlie - 3.9
​ ​ ​ Alice - 3.5
​ ​ ​ David - 3.2

🧠 Explanation:
●​ [Link](Student::getGpa).reversed() → sort by GPA descending
●​ thenComparing(Student::getName) → if GPAs are equal, sort by name ascending
●​ collect([Link]()) → collect the sorted stream into a list
​ ​ ​ ​ Notes By Sandip Vargale
🔸 Example 5: You Can Also Sort By:
​ .id → [Link](Student::getId)
​ .name length → [Link](s -> [Link]().length())
​ ​
🔸 Example 6:
​ List<String> names = [Link]("John", "Alice", "Bob");
​ List<String> sortedNames = [Link]()
​ ​ ​ ​ ​ ​ ​ .sorted()
​ ​ ​ ​ ​ ​ ​ ..toList();
​ [Link](sortedNames); // Output: [Alice, Bob, John]
​ ​
🔸 Example 7:
​ [Link]("banana", "apple", "cherry")
.sorted([Link]())
.forEach([Link]::println);​

🔹 [Link]()
​ without modifying the stream.

IP
✦ Purpose: Performs an action on each element as it is consumed from the stream ,

✦ Syntax: Stream<T> peek(Consumer<? super T> action)


ND
🔸 Example_1 :
​ ​ public class StreamExample {
​ ​ ​
public static void main(String[] args) {

​ ​ ​ ​ [Link]("apple", "banana", "cherry")


SA

​ ​ ​ ​ ​ .peek([Link]::println)
​ ​ ​ ​ ​ .collect([Link]());
​ ​ ​ }
​ ​ }
​ ​
🔸 Example_2 :
​ ​ [Link]("apple", "banana", "cherry")
​ ​ .peek([Link]::println)
​ ​ .map(String::toUpperCase)
​ ​ .forEach([Link]::println);

🧠 Real-World Use Case: Logging each transaction in a stream for debugging


purposes.
​ ​

​ ​ ​ ​ Notes By Sandip Vargale


🔹 [Link](long maxSize)
✦ Purpose: Limits the number of elements in the stream.
✦ Syntax: Stream<T> limit(long maxSize)
🔸 Example 1:
​ ​ [Link](1, 2, 3, 4, 5)
​ ​ .limit(3)
​ ​ .forEach([Link]::println);

🧠 Real-time Use: Fetching only top 3 search results.
Displaying only the top 5 trending products.

🔹 [Link](long n)
✦ Purpose: Skips the first N elements of the stream.
✦ Syntax: Stream<T> skip(long n);​
🔸 Example 1 :
​ List<Integer> numbers = [Link](1, 2, 3, 4, 5, 6);

IP
​ List<Integer> skipped = [Link]()
.skip(3)
​ ​ ​ ​ ​ ​ .collect([Link]());
​ ​ ​ [Link](skipped); // Output: [4, 5, 6]
ND
​ ​
🧠 Real-World Use Case:
●​ Paginating through a list of records by skipping the first n items.
e.g. Implementing pagination (e.g., page 2 starts after skipping 10 elements).

🔹 [Link]()
​ ​ ​

✦ Purpose: Converts elements to an IntStream. Specialized mapping for primitive


SA

types.
✦ Syntax: IntStream mapToInt(ToIntFunction<? super T> mapper);
🔸 Example :
​ ​ List<String> numbers = [Link]("1", "2", "3");

​ ​ int sum = [Link]()


​ ​ ​ ​ ​ .mapToInt(Integer::parseInt)
​ ​ ​ ​ ​ .sum();
​ ​ [Link](sum); // Output: 6

🧠 Real-World Use Case: Calculating the total of a list of transaction amounts.


🔹 [Link]()
​ ​

✦ Purpose : Converts elements to a LongStream.


✦ Syntax : LongStream mapToLong(ToLongFunction<? super T> mapper);
​ ​

​ ​ ​ ​ Notes By Sandip Vargale


🔸 Example:
​ ​ List<String> numbers = [Link]("10000000000", "20000000000");
​ ​ long sum = [Link]()
​ ​ ​ ​ ​ .mapToLong(Long::parseLong)
​ ​ ​ ​ ​ .sum();

​ ​ [Link](sum); // Output: 30000000000


​ ​ ​
🧠 Real-World Use Case: Summing large transaction amounts in a financial
application.

🔹 [Link]()
✦ Purpose : Converts elements to a DoubleStream.
✦ Syntax : DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

🔸 Example:

IP
​ ​ List<String> numbers = [Link]("1.1", "2.2", "3.3");
​ ​ double average = [Link]()
​ ​ ​ ​ ​ .mapToDouble(Double::parseDouble)
​ ​ ​ ​ ​ .sum();

🔹
ND
What are IntStream, LongStream, and DoubleStream?
●​ They are specialized stream types in Java’s Stream API designed to work
efficiently with primitive data types: int, long, and double, respectively.

🔸 Why specialized streams?


●​ Regular streams like Stream<Integer> involve boxing and unboxing (wrapping
SA

primitives in objects), which adds overhead.


●​ Primitive streams avoid this by working directly with primitives, improving
performance and memory usage.

🔸 Example:
IntStream intStream = [Link](1, 2, 3, 4);
int sum = [Link](); // 10

LongStream longStream = [Link](1, 5);


long max = [Link]().orElse(0); // 4

DoubleStream doubleStream = [Link](1.5, 2.5, 3.5);


double avg = [Link]().orElse(0.0); // 2.5

​ ​ ​ ​ Notes By Sandip Vargale


IP
ND
SA

💡 Food for Thought!


If mapToInt() gives you an IntStream,​
👉 How would you convert it back into a Stream<Integer>?
💡 Hint: There's an intermediate method that does exactly this.

THANK YOU
​ ​ ​ ​ Notes By Sandip Vargale

Common questions

Powered by AI

In Java Streams, the 'flatMap' function is comparable to SQL JOIN operations. Just as a JOIN query combines rows from different tables into a single result set, 'flatMap' takes each element of a stream, which itself is a stream or collection, and merges all these nested collections into a single stream. This is akin to flattening multiple lists into one single list. For example, if you have department data with lists of employee names, 'flatMap' would take all these lists and compile them into one comprehensive list .

The 'sorted' function in Java Streams organizes elements into a specified order, either natural or custom. When using the default 'sorted' method, elements are arranged in their natural order, like numerical or lexicographic order. However, with a custom comparator, you can define an explicit sort order. For instance, sorting students by GPA descending and then by their name requires a Comparator to specify this multi-level sorting, achieving results beyond natural ordering .

Specialized stream types, such as IntStream, LongStream, and DoubleStream, are used in Java to efficiently process primitive data types by avoiding the overhead of boxing and unboxing operations associated with working objects like Integer, Long, or Double. These streams provide optimized performance and lower memory usage since they handle primitive values directly, making operations such as sum, max, and average more efficient .

The 'peek' method in Java Streams is used for debugging or logging, as it allows actions to be performed on each stream element without altering the stream itself. It's typically utilized to inspect elements as they pass through the pipeline. For instance, in a data processing stream, using 'peek(System.out::println)' prints each element to the console, helping identify logical or data issues without affecting the data flow or outcome .

Intermediate operations in the Java Stream API, such as 'filter' and 'map', transform a stream into another stream without triggering processing immediately. They are characterized by lazy evaluation, meaning they don't execute until a terminal operation, like 'collect' or 'forEach', is called. Intermediate operations also support a fluent style where multiple operations can be chained together and always return a new stream. In contrast, terminal operations trigger the processing of data and result in a concrete value or collection .

The 'distinct' function in Java Streams is used to remove duplicates from a stream based on the equals() and hashCode() methods of the elements. For example, if you have a list of 'Student' objects, some being duplicates, invoking 'distinct()' results in a stream where each student appears only once. This is crucial for operations like eliminating duplicate entries, ensuring unique results from datasets such as lists of email addresses .

The 'limit' and 'skip' methods in Java Streams can be effectively used together to implement pagination, where a list of items is divided into pages of data. Using 'skip(n)' bypasses the first n elements, and 'limit(m)' ensures only m elements follow, allowing control over the subset of stream data to process or display, such as fetching items from the nth page in a web application .

The 'mapToInt' method in Java Streams converts a stream of objects to an IntStream, facilitating direct operations on integers without boxing overhead, thus optimizing performance. A practical example is calculating the total of transaction amounts stored as strings; 'mapToInt(Integer::parseInt)' converts these to integers, allowing efficient summation .

The 'map' operation in Java Streams is an intermediate operation that transforms each element of a stream from type T to another type R. It applies a given function to each element, returning a new stream of transformed elements. Typical use cases include converting strings to uppercase, transforming a list of objects to a list of specific fields, or converting one data structure into another, such as mapping data transfer objects .

Challenges with Java Stream's 'distinct' method arise when objects don't properly override equals() and hashCode(), as 'distinct' relies on these methods to determine uniqueness. If not properly implemented, duplicates might not be removed. Mitigation involves ensuring these methods are correctly overridden. Alternatively, a custom uniqueness criterion might be achieved using collectors or filtering mechanisms based on specific attributes .

You might also like