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.5.0-M2</version>
</dependency>
Make sure to get the latest version of commons-collections4.
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.
