1. Overview
In this short tutorial, we’ll have a look at different ways of filtering a Collection in Java – that is, finding all the items that meet a certain condition.
This is a fundamental task that is present in practically any Java application.
For this reason, the number of libraries that provide functionality for this purpose is significant.
Particularly, in this tutorial we’ll cover:
- Java 8 Streams’ filter() function
- Java 9 filtering collector
- Relevant Eclipse Collections APIs
- Apache’s CollectionUtils filter() method
- Guava’s Collections2 filter() approach
2. Using Streams
Since Java 8 was introduced, Streams have gained a key role in most cases where we have to process a collection of data.
Consequently, this is the preferred approach in most cases as it is built in Java and requires no additional dependencies.
2.1. Filtering a Collection with Streams
For the sake of simplicity, in all the examples our objective will be to create a method that retrieves only the even numbers from a Collection of Integer values.
Thus, we can express the condition that we’ll use to evaluate each item as ‘value % 2 == 0‘.
In all the cases, we’ll have to define this condition as a Predicate object:
public Collection<Integer> findEvenNumbers(Collection<Integer> baseCollection) {
Predicate<Integer> streamsPredicate = item -> item % 2 == 0;
return baseCollection.stream()
.filter(streamsPredicate)
.collect(Collectors.toList());
}
It’s important to note that each library we analyze in this tutorial provides its own Predicate implementation, but that still, all of them are defined as functional interfaces, therefore allowing us to use Lambda functions to declare them.
In this case, we used a predefined Collector provided by Java that accumulates the elements into a List, but we could’ve used others, as discussed in this previous post.
2.2. Filtering After Grouping a Collection in Java 9
Streams allow us to aggregate items using the groupingBy collector.
Yet, if we filter as we did in the last section, some elements might get discarded in an early stage, before this collector comes into play.
For this reason, the filtering collector was introduced with Java 9, with the objective of processing the subcollections after they have been grouped.
Following our example, let’s imagine we want to group our collection based on the number of digits each Integer has, before filtering out the odd numbers:
public Map<Integer, List<Integer>> findEvenNumbersAfterGrouping(
Collection<Integer> baseCollection) {
Function<Integer, Integer> getQuantityOfDigits = item -> (int) Math.log10(item) + 1;
return baseCollection.stream()
.collect(groupingBy(
getQuantityOfDigits,
filtering(item -> item % 2 == 0, toList())));
}
In short, if we use this collector, we might end up with an empty value entry, whereas if we filter before grouping, the collector wouldn’t create such an entry at all.
Of course, we would choose the approach based on our requirements.
3. Using Eclipse Collections
We can also make use of some other third-party libraries to accomplish our objective, either if its because our application doesn’t support Java 8 or because we want to take advantage of some powerful functionality not provided by Java.
Such is the case of Eclipse Collections, a library that strives to keep up with the new paradigms, evolving and embracing the changes introduced by all the latest Java releases.
We can begin by exploring our Eclipse Collections Introductory post to have a broader knowledge of the functionality provided by this library.
3.1. Dependencies
Let’s begin by adding the following dependency to our project’s pom.xml:
<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections</artifactId>
<version>9.2.0</version>
</dependency>
The eclipse-collections includes all the necessary data structure interfaces and the API itself.
3.2. Filtering a Collection with Eclipse Collections
Let’s now use eclipse’s filtering functionality on one of its data structures, such as its MutableList:
public Collection<Integer> findEvenNumbers(Collection<Integer> baseCollection) {
Predicate<Integer> eclipsePredicate
= item -> item % 2 == 0;
Collection<Integer> filteredList = Lists.mutable
.ofAll(baseCollection)
.select(eclipsePredicate);
return filteredList;
}
As an alternative, we could’ve used the Iterate‘s select() static method to define the filteredList object:
Collection<Integer> filteredList
= Iterate.select(baseCollection, eclipsePredicate);
4. Using Apache’s CollectionUtils
To get started with Apache’s CollectionUtils library, we can check out this short tutorial where we covered its uses.
In this tutorial, however, we’ll focus on its filter() implementation.
4.1. Dependencies
First, we’ll need the following dependencies in our pom.xml file:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.2</version>
</dependency>
4.2. Filtering a Collection with CollectionUtils
We are now ready to use the CollectonUtils‘ methods:
public Collection<Integer> findEvenNumbers(Collection<Integer> baseCollection) {
Predicate<Integer> apachePredicate = item -> item % 2 == 0;
CollectionUtils.filter(baseCollection, apachePredicate);
return baseCollection;
}
We have to take into account that this method modifies the baseCollection by removing every item that doesn’t match the condition.
This means that the base Collection has to be mutable, otherwise it will throw an exception.
5. Using Guava’s Collections2
As before, we can read our previous post ‘Filtering and Transforming Collections in Guava’ for further information on this subject.
5.1. Dependencies
Let’s start by adding this dependency in our pom.xml file:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
5.2. Filtering a Collection with Collections2
As we can see, this approach is fairly similar to the one followed in the last section:
public Collection<Integer> findEvenNumbers(Collection<Integer> baseCollection) {
Predicate<Integer> guavaPredicate = item -> item % 2 == 0;
return Collections2.filter(baseCollection, guavaPredicate);
}
Again, here we define a Guava specific Predicate object.
In this case, Guava doesn’t modify the baseCollection, it generates a new one, so we can use an immutable collection as input.
6. Conclusion
In summary, we’ve seen that there are many different ways of filtering collections in Java.
Even though Streams are usually the preferred approach, its good to know and keep in mind the functionality offered by other libraries.
Especially if we need to support older Java versions. However, if this is the case, we need to keep in mind recent Java features used throughout the tutorial such as lambdas should be replaced with anonymous classes.
As usual, we can find all the examples shown in this tutorial in our Github repo.