What is a Stream?
A Stream in Java is like a flow of data. Imagine water flowing through a pipe; the water is the data, and the pipe is the stream. You can think of streams as a way to work with lots of data one piece at a time without storing it all in memory.
Why Use Streams?
Streams help you work with collections (like lists and sets) in a clean and efficient way. They let you perform operations like filtering, sorting, and transforming data with less code.
Key Parts of a Stream
-
Source:
- This is where the stream gets its data. It can come from a list, array, file, or other places.
-
Intermediate Operations:
- These are steps that change or filter the stream. They are like checkpoints where you can modify the data. These operations are lazy, meaning they only work when you tell the stream to finish (with a terminal operation).
- Examples:
filter
,map
,sorted
.
-
Terminal Operations:
- These are steps that finish the stream and produce a result. When you call a terminal operation, it processes all the intermediate steps.
- Examples:
forEach
,collect
,reduce
.
Creating a Stream
You can create a stream from different sources:
-
From a List:
List
list = Arrays.asList("apple", "banana", "cherry"); Stream stream = list.stream(); -
From an Array:
String[] array = {"apple", "banana", "cherry"}; Stream
stream = Arrays.stream(array); -
From a File:
Stream
stream = Files.lines(Paths.get("file.txt"));
Intermediate Operations
Intermediate operations transform the stream but do not produce a final result. Here are some common ones:
-
filter:
- Keeps only the elements that match a condition.
Stream
filteredStream = stream.filter(s -> s.startsWith("a"));
- Keeps only the elements that match a condition.
-
map:
- Changes each element using a function.
Stream
lengthStream = stream.map(String::length);
- Changes each element using a function.
-
sorted:
- Sorts the elements.
Stream
sortedStream = stream.sorted();
- Sorts the elements.
Terminal Operations
Terminal operations produce a result or side effect. Here are some examples:
-
forEach:
- Does something with each element, like printing it.
stream.forEach(System.out::println);
- Does something with each element, like printing it.
-
collect:
- Gathers the elements into a collection, like a list.
List
result = stream.collect(Collectors.toList());
- Gathers the elements into a collection, like a list.
-
reduce:
- Combines all elements into one, like adding numbers together.
Optional
concatenated = stream.reduce((s1, s2) -> s1 + s2);
- Combines all elements into one, like adding numbers together.
Example in Simple Steps
Here’s a complete example to show how it works:
-
Start with a List:
List
fruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry"); -
Create a Stream:
Stream
fruitStream = fruits.stream(); -
Apply Intermediate Operations:
-
Filter to keep fruits that start with "a" or "e":
Stream
filteredStream = fruitStream.filter(s -> s.startsWith("a") || s.startsWith("e")); -
Sort the filtered fruits:
Stream
sortedStream = filteredStream.sorted();
-
-
Apply a Terminal Operation:
- Collect the sorted fruits into a list:
List
result = sortedStream.collect(Collectors.toList());
- Collect the sorted fruits into a list:
-
Print the Result:
result.forEach(System.out::println);
Putting It All Together
Here’s the entire example in one piece:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
List<String> result = fruits.stream() // Create a stream from the list
.filter(s -> s.startsWith("a") || s.startsWith("e")) // Filter the stream
.sorted() // Sort the stream
.collect(Collectors.toList()); // Collect the result into a list
result.forEach(System.out::println); // Print each element in the result list
}
}
Parallel Streams
If you have a lot of data and want to process it faster, you can use parallel streams. This makes the stream operations run on multiple threads.
List<String> list = Arrays.asList("apple", "banana", "cherry");
Stream<String> parallelStream = list.parallelStream();
Parallel streams can speed up processing but can also be tricky if not used correctly. They work best when operations are independent and can run safely at the same time.
Conclusion
The Stream API in Java helps you work with data in a simple, readable, and efficient way. By using streams, you can write less code and make it easier to understand and maintain. The key is to use intermediate operations to transform the data and terminal operations to produce the final result.