What is Stream API in Java?
The Java Stream API, introduced in Java 8, provides a powerful and functional approach to process collections of objects. It enables developers to perform complex data manipulation operations in a more declarative, concise, and often parallelizable manner.
What is the Stream API?
The Java Stream API is not a data structure itself; instead, it's a sequence of elements that supports sequential and parallel aggregate operations. Streams work with data sources like collections, arrays, or I/O channels, allowing operations like filtering, mapping, and reducing without modifying the source. It facilitates a functional programming style for processing data.
Key Characteristics
- Functional in nature: Operations are expressed as lambda expressions or method references.
- Laziness: Intermediate operations are not executed until a terminal operation is invoked.
- Pipelining: Multiple intermediate operations can be chained together to form a pipeline.
- No storage: Streams do not store elements; they are computed on demand from a source.
- Single-use: A stream can be traversed only once; attempting to reuse it will throw an IllegalStateException.
- Non-interfering: Stream operations do not modify the underlying data source.
- Terminal vs. Intermediate operations: Operations are categorized into intermediate (return a new stream) and terminal (produce a result or a side-effect).
Stream Operations
Intermediate Operations
These operations transform a stream into another stream. They are always lazy, meaning they are not executed until a terminal operation is invoked. Multiple intermediate operations can be chained together to form a pipeline.
filter(Predicate<T>): Selects elements that match a given predicate.map(Function<T, R>): Transforms each element of the stream by applying a function.flatMap(Function<T, Stream<R>>): Transforms each element into a stream of zero or more other elements, then flattens the resulting streams into a single stream.distinct(): Returns a stream consisting of the distinct elements.sorted()/sorted(Comparator<T>): Returns a stream consisting of the elements sorted according to natural order or a provided Comparator.limit(long maxSize): Returns a stream consisting of the elements of this stream, truncated to be no longer thanmaxSize.skip(long n): Returns a stream consisting of the remaining elements after discarding the firstnelements.
Terminal Operations
These operations produce a non-stream result or a side-effect, and terminate the stream pipeline. Once a terminal operation is performed, the stream is consumed and cannot be reused.
forEach(Consumer<T>): Performs an action for each element of this stream.collect(Collector<T, A, R>): Accumulates the elements into a Collection or summary result.reduce(BinaryOperator<T>): Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the result.count(): Returns the count of elements in this stream.min(Comparator<T>)/max(Comparator<T>): Returns an Optional describing the minimum/maximum element of this stream according to the provided Comparator.anyMatch(Predicate<T>),allMatch(Predicate<T>),noneMatch(Predicate<T>): Return a boolean indicating if any, all, or none of the elements match the given predicate.findFirst()/findAny(): Returns an Optional describing the first element of this stream, or any element of this stream, respectively.
Example Usage
Let's consider an example where we filter a list of numbers, double the even ones, and then collect them into a new list.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter even numbers, double them, and collect into a new list
List<Integer> evenDoubledNumbers = numbers.stream()
.filter(n -> n % 2 == 0) // Intermediate operation
.map(n -> n * 2) // Intermediate operation
.collect(Collectors.toList()); // Terminal operation
System.out.println(evenDoubledNumbers); // Output: [4, 8, 12, 16, 20]
}
}
Benefits of Stream API
- Concise and Readable Code: Reduces boilerplate compared to traditional loop-based processing, leading to more compact and understandable code.
- Declarative Style: Focuses on *what* to do rather than *how* to do it, making the intent of the code clearer.
- Parallel Processing: Offers built-in support for parallel execution (
parallelStream()), which can significantly improve performance on multi-core processors for large datasets. - Reduced Boilerplate: Eliminates explicit loops, iterators, and conditional statements for many common data processing tasks.
- Functional Programming Style: Encourages the use of lambda expressions and method references, promoting a more functional and less imperative programming paradigm.