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:
Listlist = Arrays.asList("apple", "banana", "cherry"); Stream stream = list.stream(); -
From an Array:
String[] array = {"apple", "banana", "cherry"}; Streamstream = Arrays.stream(array); -
From a File:
Streamstream = 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.
StreamfilteredStream = stream.filter(s -> s.startsWith("a"));
- Keeps only the elements that match a condition.
-
map:
- Changes each element using a function.
StreamlengthStream = stream.map(String::length);
- Changes each element using a function.
-
sorted:
- Sorts the elements.
StreamsortedStream = 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.
Listresult = stream.collect(Collectors.toList());
- Gathers the elements into a collection, like a list.
-
reduce:
- Combines all elements into one, like adding numbers together.
Optionalconcatenated = 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:
Listfruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry"); -
Create a Stream:
StreamfruitStream = fruits.stream(); -
Apply Intermediate Operations:
-
Filter to keep fruits that start with "a" or "e":
StreamfilteredStream = fruitStream.filter(s -> s.startsWith("a") || s.startsWith("e")); -
Sort the filtered fruits:
StreamsortedStream = filteredStream.sorted();
-
-
Apply a Terminal Operation:
- Collect the sorted fruits into a list:
Listresult = 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.