Introduction
The Java Stream API is a powerful feature introduced in Java 8 that simplifies data processing tasks. It enables developers to write clean, concise, and efficient code by processing data in a functional programming style. This blog will walk you through the basics of the Java Stream API, with detailed explanations and easy-to-understand examples to help you get started.
Table of Contents
What is a Stream?
How to Create Streams
Stream Operations: Intermediate vs. Terminal
Common Stream Operations with Examples
Filtering
Mapping
Sorting
Reducing
Working with Parallel Streams
Best Practices for Using Streams
Conclusion
1. What is a Stream?
A Stream in Java is a sequence of elements that can be processed in parallel or sequentially. Unlike collections, a stream does not store elements; instead, it carries elements from a source (like a collection or array) through a pipeline of operations. The Stream API allows you to perform operations like filtering, mapping, and reducing in a clean and declarative way.
Think of a stream as a conveyor belt in a factory: you place raw materials on one end, they go through various processing stages, and you get the finished product at the other end.
2. How to Create Streams
Streams can be created from various data sources, such as collections, arrays, or even from values directly. Here are a few ways to create streams:
From a Collection
You can create a stream from any Java Collection (like List, Set, etc.) using the stream()Â method.
List<String> names = Arrays.asList("Pankaj", "Amit", "Rahul");
Stream<String> stream = names.stream();
From an Array
Similarly, you can create a stream from an array using the Arrays.stream()Â method.
String[] nameArray = {"Pankaj", "Amit", "Rahul"};
Stream<String> stream = Arrays.stream(nameArray);
Using Stream.of()
The Stream.of()Â method allows you to create a stream directly from a set of values.
Stream<String> stream = Stream.of("Pankaj", "Amit", "Rahul");
3. Stream Operations: Intermediate vs. Terminal
Stream operations are categorized into two types:
Intermediate Operations: These operations return a new stream and are lazy, meaning they are not executed until a terminal operation is invoked. Examples include filter(), map(), and sorted().
Terminal Operations: These operations produce a result or a side effect and terminate the stream. Examples include forEach(), collect(), and reduce().
4. Common Stream Operations with Examples
Let’s dive into some of the most common stream operations with examples that are easy to follow.
4.1 Filtering
Filtering is one of the most commonly used operations, allowing you to select elements that meet a certain condition. The filter() method takes a predicate (a function that returns true or false) as an argument.
Example: Filtering Even Numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0)
// Keep only even number
.collect(Collectors.toList());
System.out.println(evenNumbers); // Output: [2, 4, 6]
System.out.println(evenNumbers); // Output: [2, 4, 6]
Explanation:
The stream is created from the numbers list.
filter(n -> n % 2 == 0)Â selects only the even numbers.
The result is collected into a new list using collect(Collectors.toList()).
4.2 Mapping
Mapping transforms each element of the stream into another form using the map()Â method.
Example: Converting Names to Uppercase
List<String> names = Arrays.asList("Pankaj", "Amit", "Rahul");
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase) // Convert each name to uppercase
.collect(Collectors.toList());
System.out.println(upperCaseNames); // Output: [PANKAJ, AMIT, RAHUL]
Explanation :
The stream is created from the names list.
map(String::toUpperCase)Â converts each string to uppercase.
The result is collected into a new list.
4.3 Sorting
Sorting can be done using the sorted()Â method, which returns a new stream with sorted elements.
Example: Sorting Names Alphabetically
List<String> names = Arrays.asList("Pankaj", "Amit", "Rahul");
List<String> sortedNames = names.stream()
.sorted() // Sort names alphabetically
.collect(Collectors.toList());
System.out.println(sortedNames); // Output: [Amit, Pankaj, Rahul]
Explanation :
The stream is created from the names list.
sorted()Â sorts the elements in natural order (alphabetical for strings).
The result is collected into a new list.
4.4 Reducing
The reduce()Â method combines elements of a stream to produce a single result. This is often used to sum elements, find a maximum, or concatenate strings.
Example : Summing Numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum); // Sum all numbers
System.out.println(sum); // Output: 15
Explanation :
The stream is created from the numbers list.
reduce(0, Integer::sum)Â starts with an initial value of 0Â and sums all elements in the stream.
5. Working with Parallel Streams
Parallel streams allow you to process data in parallel, utilizing multiple CPU cores. You can create a parallel stream by using the parallelStream()Â method.
Example: Parallel Stream for Summing Numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
int sum = numbers.parallelStream()
.reduce(0, Integer::sum);
System.out.println(sum); // Output: 36
Explanation:
The parallelStream()Â method creates a parallel stream.
The reduce()Â method then sums the numbers in parallel.
6. Best Practices for Using Streams
Use parallel streams with caution: While parallel streams can enhance performance, they may also introduce concurrency issues and reduce performance if not used correctly.
Avoid stateful lambda expressions: Stateless operations are preferred because they avoid potential issues with concurrent modifications.
Minimize side-effects: Streams are designed for functional programming, so side-effects should be avoided to ensure clean and predictable code.
Complete Example: Processing a List of People
Imagine you have a list of people, and you want to:
Filter out those who are under 18 years old.
Convert their names to uppercase.
Sort them by name.
Collect the final list and print it.
Here’s how you can achieve this using the Java Stream API.
import java.util.*;
import java.util.stream.Collectors;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class StreamExample {
public static void main(String[] args) {
// Create a list of people
List<Person> people = Arrays.asList(
new Person("Pankaj", 30),
new Person("Amit", 25),
new Person("Rahul", 17),
new Person("Ravi", 20),
new Person("Sneha", 15)
);
// Process the list using Stream API
List<String> result = people.stream()
.filter(person -> person.getAge() >= 18)
// 1. Filter: Keep only adults
.map(Person::getName)
// 2. Map: Get the name of each person
.map(String::toUpperCase)
// 3. Map: Convert names to uppercase
.sorted()
// 4. Sort: Sort names alphabetically
.collect(Collectors.toList());
// 5. Collect: Collect the result into a list
// Print the result
System.out.println(result);
// Output: [AMIT, PANKAJ, RAVI]
}
}
Explanation of the Example
Person Class:
We define a Person class with name and age attributes. It has a constructor, getters for the attributes, and a toString() method for easy printing.
Creating a List of People:
We create a list of Person objects, each representing a person with a name and age.
Using the Stream API:
We convert the list to a stream using people.stream().
Filter: We use filter(person -> person.getAge() >= 18)Â to keep only those who are 18 or older.
Map (Name Extraction): We extract the names of the remaining people using map(Person::getName).
Map (Uppercase Conversion): We convert the names to uppercase using map(String::toUpperCase).
Sort: We sort the names alphabetically using sorted().
Collect: Finally, we collect the results into a new list using collect(Collectors.toList()).
Printing the Result:
The result, which is a list of uppercase names of adults sorted alphabetically, is printed to the console.
7. Conclusion
The Java Stream API is a powerful tool that allows you to process data in a declarative and functional style. By understanding the basic concepts and operations, you can write more readable, maintainable, and efficient code. Whether you are filtering, mapping, or reducing data, the Stream API simplifies your tasks and enables you to focus on what your code should do rather than how it should do it.
Comentarios