Introduction
In Java, collections are fundamental structures used to store groups of objects. Often, developers need to filter these collections based on specific criteria or predicates—functions that return a boolean value. This tutorial explores different techniques for filtering Java collections using predicates, from pre-Java 8 approaches to modern solutions leveraging Java Streams introduced in Java 8.
Filtering Collections with Java 1.5 and Earlier
Before Java 8, developers had to manually iterate through collections and apply custom logic to filter elements. Here’s a basic approach:
Implementing Custom Predicates
First, define an interface for predicates:
public interface IPredicate<T> {
boolean apply(T type);
}
Implement a method to perform filtering:
public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
Collection<T> result = new ArrayList<>();
for (T element : target) {
if (predicate.apply(element)) {
result.add(element);
}
}
return result;
}
Usage Example
Suppose we want to filter a collection of users based on authorization status:
Predicate<User> isAuthorized = user -> user.isAuthorized();
Collection<User> authorizedUsers = filter(allUsers, isAuthorized);
This manual approach requires iterating over the collection and applying logic via the IPredicate
interface.
Java 8: Leveraging Streams and Lambdas
Java 8 introduced streams, a significant enhancement that simplifies collection processing. Using streams with lambda expressions allows for concise and readable code to filter collections.
Basic Filtering with Streams
Convert a collection into a stream, apply filtering, and collect results:
List<Person> beerDrinkers = persons.stream()
.filter(p -> p.getAge() > 16)
.collect(Collectors.toList());
This approach uses stream()
, filter()
, and collect()
methods for streamlined operations.
Modifying Collections In-Place
Java 8 also provides a way to modify collections in place using the removeIf
method:
persons.removeIf(p -> p.getAge() <= 16);
This removes elements directly from the original collection based on the specified predicate.
Third-party Libraries: Apache Commons
For those preferring third-party libraries, Apache Commons provides utilities for common operations like filtering collections. The CollectionUtils.filter
method can be used as follows:
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
Collection<Person> filtered = CollectionUtils.filter(persons,
new Predicate<Person>() {
public boolean evaluate(Person person) {
return person.getAge() > 16;
}
});
While powerful, using such libraries can introduce additional dependencies into your project.
Advanced Java 8 Techniques
Java 8’s Optional
class and stream API offer sophisticated techniques for dealing with filtered results:
Using Optional for Single Results
To find the first element that matches a condition:
UserService userService = ... // Service to check authorization
Optional<UserModel> userOption = userCollection.stream()
.filter(u -> userService.isAuthorized(u))
.findFirst();
Optional
provides methods like orElse
, orElseGet
, and ifPresent
for handling default values or side effects.
Collecting Results into a New Collection
To gather all matching elements:
List<UserModel> authorizedUsers = userCollection.stream()
.filter(u -> userService.isAuthorized(u))
.collect(Collectors.toList());
Conclusion
Filtering Java collections is a common task that has been greatly simplified with the introduction of streams and lambda expressions in Java 8. Whether using manual iteration, third-party libraries like Apache Commons, or modern techniques provided by Java’s standard library, there are multiple approaches to achieving efficient and readable collection filtering.
By understanding these methods, you can choose the most appropriate one based on your project requirements, ensuring maintainable and clear code.