Table of Contents
ToggleIntroduction
The Stream API is a powerful addition to Java that enables efficient processing of sequences of elements, such as collections or arrays, in a functional style. Stream operations in Java allow developers to write concise, readable, and maintainable code to perform various operations like filtering, transforming, and aggregating data. In this tutorial, we will explore the Top 10 Operations in Stream API, focusing on the most essential and frequently used ones.
What Is a Stream in Java?
A stream is a sequence of objects that supports various methods, which can be pipelined to produce the desired result. The primary benefits of streams include the ability to work with collections in a more functional way, without modifying the underlying data structure. The operations in the Stream API are divided into two categories: Intermediate Operations and Terminal Operations.
Intermediate Operations
Intermediate operations return another stream, allowing for method chaining. These operations are lazy and don’t execute until a terminal operation is invoked.
Terminal Operations
Terminal operations produce a result (like a List, Set, or boolean) or a side effect, and they end the stream pipeline.
Why Use Stream Operations in Java?
Stream operations in Java simplify data processing tasks, reducing boilerplate code. Here’s why using streams is beneficial:
- Concise and Readable Code: Stream operations reduce the need for loops and conditionals, making the code easier to understand.
- Parallel Processing: Streams support parallel operations, making them efficient for large datasets.
- Functional Style Programming: Streams align well with functional programming paradigms, allowing you to focus on what you want to achieve rather than how to achieve it.
Top 10 Operations in Stream API
Now, let’s dive into the Top 10 Operations in Stream API that you should know to enhance your Java programming skills.
1. filter()
The filter() method filters out elements based on a given condition (predicate). It’s one of the most commonly used stream operations in Java to eliminate unwanted data.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // Output: [2, 4, 6]
In Spring Boot, you can use filter() in service layers when working with collections of data entities.
2. map()
The map() method transforms each element in the stream using a function. This method transforms data from one format to a different one..
List<String> names = Arrays.asList("john", "doe", "smith");
List<String> capitalizedNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(capitalizedNames); // Output: [JOHN, DOE, SMITH]
3. collect()
collect() is a terminal operation that gathers the elements of the stream into a collection (such as a List or Set).
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> collectedNames = names.stream()
.collect(Collectors.toList());
System.out.println(collectedNames); // Output: [Alice, Bob, Charlie]
In Spring Boot, you can collect data from streams into custom response objects to return in API calls.
4. forEach()
forEach() is a terminal operation that applies a specified action to each element of the stream.
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
fruits.stream().forEach(System.out::println);
5. findFirst()
The `findFirst()` method retrieves the first element of the stream and wraps it in an `Optional`. This is useful when you need the first match based on a certain condition.
Optional<String> firstFruit = fruits.stream().findFirst();
firstFruit.ifPresent(System.out::println); // Output: Apple
6. reduce()
reduce() is used to aggregate stream elements into a single result. It’s a powerful operation for summing, concatenating, or performing other accumulation tasks.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum); // Output: 15
In Spring Boot, you might use reduce() when summarizing the data fetched from repositories.
7. sorted()
sorted() sorts the elements of the stream based on natural order or a custom comparator.
List<Integer> numbers = Arrays.asList(5, 3, 8, 1);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // Output: [1, 3, 5, 8]
8. distinct()
distinct() eliminates duplicates in the stream.
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> uniqueNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueNumbers); // Output: [1, 2, 3, 4, 5]
9. limit()
limit() restricts the number of elements in the stream to the specified value.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> limitedNames = names.stream()
.limit(2)
.collect(Collectors.toList());
System.out.println(limitedNames); // Output: [Alice, Bob]
10. anyMatch()
anyMatch() checks if any element in the stream matches a given predicate.
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
System.out.println(hasEven); // Output: true
Stream Operations in Java and Spring Boot Example
Let’s combine these stream operations into a Spring Boot example to demonstrate how you can use them in a real-world scenario. Imagine a simple API that filters and sorts a list of users based on age. To learn more about Spring Boot. follow my Spring Boot blog page.
Controller Example
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/filter")
public List<User> getFilteredUsers() {
List<User> users = userService.getAllUsers();
return users.stream()
.filter(user -> user.getAge() > 18)
.sorted(Comparator.comparing(User::getName))
.collect(Collectors.toList());
}
}
Service Example
@Service
public class UserService {
public List<User> getAllUsers() {
// Mock data for example purposes
return Arrays.asList(
new User("John", 25),
new User("Doe", 17),
new User("Smith", 30)
);
}
}
User Class Example
public class User {
private String name;
private int age;
// Constructor, Getters, Setters
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
In this Spring Boot example, we’ve demonstrated how you can use stream operations like filter(), sorted(), and collect() to handle and manipulate data in a typical REST API.
FAQs
1. What are the most commonly used Stream Operations in Java?
The most commonly used stream operations are filter(), map(), collect(), forEach(), and reduce().
2. Can Stream API operations be used with custom objects?
Yes, stream operations in Java can be applied to custom objects by providing the appropriate functions or comparators.
3. Is Stream API supported in older Java versions?
Stream API was introduced in Java 8, so it is not available in versions before Java 8.
4. How does parallel processing work with Streams?
Streams support parallel processing with the parallelStream() method, which divides the work across multiple threads.
5. What is the difference between intermediate and terminal operations in Stream API?
Intermediate operations return another stream and are lazy, while terminal operations produce a result or side effect and end the stream pipeline.
Conclusion
In this tutorial, we’ve explored the Top 10 Operations in Stream API in detail, with practical examples in Java and Spring Boot. Stream operations in Java offer a powerful way to handle data, making your code more efficient and readable. By mastering these stream operations, you can significantly improve your programming skills and write high-performance applications.