☕ Java Q28 / 105

What is Stream API in Java?

AI-Powered Answer ✓ Answered

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 than maxSize.
  • skip(long n): Returns a stream consisting of the remaining elements after discarding the first n elements.

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.

java
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.