Introduction
- Java provides various data structures under its collections umbrella.
- Filtering these collections is one of the most used operations when developing with Java.
- In this article, we will see how we can filter Java Collections using the Imperative and Declarative styles of programming
Use Case
- Let’s say we have a Movie class that has properties such as creationDate, length, producerId.
- Our goal is to filter the collection of movies based on some conditions such length of the movie. we want to filter out all the movies whose length is less than 150 hours.
public class Movie {
private String creationDate;
private Integer length;
private String producerId;
public Movie(String creationDate, Integer length, String producerId) {
this.creationDate = creationDate;
this.length = length;
this.producerId = producerId;
}
public Movie() {
}
public String getCreationDate() {
return creationDate;
}
public void setCreationDate(String creationDate) {
this.creationDate = creationDate;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
public String getProducerId() {
return producerId;
}
public void setProducerId(String producerId) {
this.producerId = producerId;
}
}
- Let’s generate mockdata first.
private static List<Movie> getMovieData(){
List<Movie> movies = new ArrayList<>();
movies.add(new Movie("2021-03-24 16:48:05.591", 125, UUID.randomUUID().toString()));
movies.add(new Movie("2021-08-24 16:48:05.591", 180, UUID.randomUUID().toString()));
movies.add(new Movie("2021-04-24 16:48:05.591", 100, UUID.randomUUID().toString()));
movies.add(new Movie("2021-09-24 16:48:05.591", 190, UUID.randomUUID().toString()));
return movies;
}
Filtering List In Java
Traditional Iterative approach
- The traditional approach is still used by many developers which is an imperative programming style. Traditional for loop is enough to iterate over and use if we can filter out data that we want in the result.
List<Movie> filteredMovies = new ArrayList<>();
for(Movie movie: getMovieData()){
if(movie.getLength()<150){
filteredMovies.add(movie);
}
}
System.out.println(filteredMovies.size());
Using Java 8 Streams API
- Java streams provide a more declarative approach to our collections. we can stream over the list and filter it using our predicate.
- Once we filtered out our input records based on predicate conditions we can then collect back the result to the List.
List<Movie> filteredMovies = getMovieData()
.stream()
.filter(movie -> movie.getLength() < 150)
.collect(Collectors.toList());
System.out.println(filteredMovies.size());
Filtering Map in Java
- We can use imperative style with Map as well as we have seen with the list so let me focus more on declarative style.
Using Java 8 Streams API
- We can stream HashMap entry using stream API and filter our result using a predicate, then get the result back to the list or any other collection as we need it.
- Let’s map the list of movies to data to HashMap. The Key will be produced and the value will be the Movie instance itself.
Map<String, List<Movie>> producerMovieMap = new HashMap<>();
getMovieData().stream()
.forEach(movie -> producerMovieMap.put(movie.getProducerId(), Arrays.asList(movie)));
- Now our task is to iterate over each entry of the hashmap and filtered all the movies that are less than 150 minutes long.
- We need to convert our hashmap into entryset first before streaming it.
Once the stream is done we can map into key and value for each entry.
For us, the key is producer id and value is a list of movies produced by the producers - Hence when we stream over each entry we can get a list of movies as value because that is how we stored our record on the map. <ProducerId, List of movie1, movie2 ..>
- So we need to filter that list and get only movies that are running for less than 150 minutes.
List<List<Movie>> filteredListOfMov = producerMovieMap
.entrySet()
.stream()
.map(e -> e.getValue()
.stream()
.filter(m -> m.getLength() < 150)
.collect(Collectors.toList()))
.collect(Collectors.toList());
- Once we filtered our movies it return us List<List> as result, we can easily flatten that using flatmap.
List<Movie> flatFilteredMovies = filteredListOfMov
.stream()
.flatMap(a -> a.stream())
.collect(Collectors.toList());
System.out.println(flatFilteredMovies.size());
Filtering Set in Java
- We can also use java stream API on HashSet and then filter the record using predicate in our case movie length less than 150 minutes.
- Then we can convert our result back to HashSet using collectors.
Set<Movie> movieSet = new HashSet<>(getMovieData());
Set<Movie> filteredMovieSet = movieSet
.stream()
.filter(movie -> movie.getLength() < 150)
.collect(Collectors.toCollection(HashSet::new));
System.out.println("set Size"+filteredMovieSet.size());
Filtering Queue in Java
- Queue implementations also support stream API to stream each record and then we can easily apply the filter as usual and supply predicates.
- Using collectors we can collect results into new Queue implementation.
Queue<Movie> queue = new ArrayDeque<>(getMovieData());
ArrayDeque<Movie> filteredMovieQueue = queue
.stream()
.filter(movie -> movie.getLength() < 150)
.collect(Collectors.toCollection(ArrayDeque::new));
System.out.println(filteredMovieQueue.size());
- Just FYI, we can also use queue method removeIf which will remove all the movies that are greater or equal to 150
Queue<Movie> queue = new ArrayDeque<>(getMovieData());
queue.removeIf(movie -> movie.getLength() >= 150);
System.out.println(queue.size());
Conclusion
- In Java, we can filter collections using imperative and declarative programming styles.
- Stream API supports all the major data structures such as List, Map, Queue, Stack, and Set, and after that, we can easily use methods of filter chaining to filter out records based on predicates.
Bonus Tip
- If you want to upskill your Java, you should definitely check out this bestseller course
Follow me on LinkedIn