Course Content
Stream API
Stream API
Principles
The principles of working with the Stream API are based on key concepts of functional programming and lazy evaluation. Let's take a look at the main principles of the Stream API.
Lazy Evaluation
One of the core principles of the Stream API is lazy evaluation.
This means that intermediate operations like filter()
or map()
are not executed immediately. They simply form a chain of actions that will be executed only when a terminal operation, such as collect()
or forEach()
, is invoked.
Main
package com.example; import java.util.List; public class Main { public static void main(String[] args) { List<String> names = List.of("Alice", "Bob", "Charlie"); long count = names.stream() .filter(name -> name.startsWith("A")) // Forming the operation .count(); // Triggering execution System.out.println(count); // Output: 1 } }
This code creates a stream from a list of strings, filters the elements to keep only those starting with the letter A
, and counts how many meet the condition. The result (1
) is printed since only Alice
satisfies the condition.
Functional Programming Style
The Stream API utilizes lambda expressions and functional interfaces for data processing. Instead of the imperative approach with loops, you describe what needs to be done with the data:
Main
package com.example; import java.util.List; public class Main { public static void main(String[] args) { List<String> fruits = List.of("apple", "banana", "cherry", "apricot", "blueberry"); List<String> result = fruits.stream() .map(String::toUpperCase) // Convert all strings to uppercase .skip(3) .toList(); // Collect the result into a new list System.out.println(result); } }
This code creates a stream from a list of fruits, converts the strings to uppercase using map()
, and skips the first three elements with skip()
. The result is collected into a new list and printed, starting from the fourth element.
Immutability of Data
The Stream API does not modify the original data. All operations create a new stream or return a result without modifying the collection or array. This enhances data safety and prevents unexpected side effects.
Main
package com.example; import java.util.List; public class Main { public static void main(String[] args) { List<String> names = List.of("Alice", "Bob", "Charlie"); List<String> updateNames = names.stream() .filter(name -> name.startsWith("A")) .toList(); System.out.println(names); // Output: [Alice, Bob, Charlie] } }
The Stream API filters the list names to create a new stream, updateNames
, that contains only elements starting with the letter A
. However, the original list names remains unchanged, as the Stream API does not modify the data directly, ensuring data safety and preventing side effects.
Streams Are Consumable Once
A stream can be used only once. After a terminal operation is performed, the stream becomes unavailable for further processing.
Main
package com.example; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<String> stream = List.of("Alice", "Bob").stream(); stream.forEach(System.out::println); stream.forEach(System.out::println); // Error: Stream has already been used } }
Here, we create a stream from a list of strings and print each element to the console. After the stream is used for the first time, it cannot be reused, which causes an error if you attempt to call forEach()
again.
Parallel Processing
The Stream API supports parallel streams, enabling faster data processing on multi-core systems. This is explored in more detail here
Main
package com.example; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> numbers = List.of(1, 2, 3, 4, 5); numbers.parallelStream() .forEach(System.out::println); // Elements are processed in parallel } }
This code creates a parallel stream from a list of numbers and prints each element to the console. Using parallelStream()
allows for parallel processing of the list elements, which can speed up execution when dealing with large datasets.
Code Cleanliness and Readability
Using the Stream API makes the code more declarative. Instead of describing how to perform a task (like using loops), you describe what exactly needs to be done. This enhances readability and simplifies code maintenance.
Example with a loop:
Example using Stream API:
In the first loop example, we explicitly describe how to iterate through the list and print the elements, while in the second example with the Stream API, we simply specify what needs to be done: iterate through the elements and print them. This makes the code more declarative and readable.
Thanks for your feedback!