Streams API
The Java Streams API is a powerful tool introduced in Java 8 to process collections of data in a clean, efficient, and functional way. Think of it as a way to handle data like lists, arrays, or sets by performing a series of operations on them, like filtering, sorting, and mapping, without using complex loops. Streams help make code shorter, more readable, and more efficient.
What is a Stream?
A stream in Java is a sequence of data elements (like items in a list) that you can perform operations on, one after the other. A stream doesn't store data itself; it just processes it, often one element at a time.
Why Use Streams?
- Simpler Code: With streams, you can write complex data processing tasks in a single line.
- Parallel Processing: Streams can run operations in parallel, making tasks faster on multi-core systems.
- Functional Style: Streams let you use a more functional style of programming, which is shorter and more readable.
Creating a Stream
You can create a stream from many data sources, like collections or arrays. Here’s how to create a stream from a list:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Stream<String> namesStream = names.stream();
Key Stream Operations
Streams work by combining several operations. These operations can be intermediate (transforming the data) or terminal (producing the final result).
Intermediate Operations (These create modified streams)
1. filter(): Keeps only the elements that match a certain condition.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); numbers.stream().filter(n -> n % 2 == 0).forEach(System.out::println); // Output: 2, 4
2. map(): Transforms each element into something else.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream().map(String::toUpperCase).forEach(System.out::println); // Output: ALICE, BOB, CHARLIE
3. sorted(): Sorts the elements of the stream.
List<String> names = Arrays.asList("Charlie", "Alice", "Bob"); names.stream().sorted().forEach(System.out::println); // Output: Alice, Bob, Charlie
Terminal Operations (These produce a result)
1. forEach(): Applies an action to each element.
names.stream().forEach(System.out::println);
2. collect(): Collects the elements into a collection like a list or a set.
List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList());
3. count(): Returns the number of elements in the stream.
long count = names.stream().count();
4. reduce(): Combines elements to produce a single result.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4); int sum = numbers.stream().reduce(0, (a, b) -> a + b); // Output: 10
Working with Parallel Streams
Streams can also be parallel, which allows the program to process data faster on multi-core processors. To create a parallel stream, just use .parallelStream():
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.parallelStream().forEach(System.out::println);
Chaining Stream Operations
One of the biggest advantages of streams is chaining multiple operations in one line. Here’s an example:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> filteredAndSorted = names.stream() .filter(name -> name.startsWith("A") || name.startsWith("B")) .map(String::toUpperCase) .sorted() .collect(Collectors.toList()); System.out.println(filteredAndSorted); // Output: [ALICE, BOB]