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:

Java
Copy
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.

Java
Copy
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.

Java
Copy
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.

Java
Copy
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.

Java
Copy
names.stream().forEach(System.out::println);

2. collect(): Collects the elements into a collection like a list or a set.

Java
Copy
List<String> upperCaseNames = names.stream()
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());

3. count(): Returns the number of elements in the stream.

Java
Copy
long count = names.stream().count();

4. reduce(): Combines elements to produce a single result.

Java
Copy
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():

Java
Copy
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:

Java
Copy
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]