Chapter 8 - Java for Beginners Course

Filtering a List

In this section we’ll introduce the example that will serve us as guide for the remainder of the chapter.

Number filtering

Assume that we have a List<Integer> with some elements and that we need to filter these data and only return numbers that are below a certain maximum (let’s assume 10 is our limit), like this:

In this example we are using Integer, but the same applies to any other type of object.
In the example code run the JavaListExampleApp
// our input list of numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 10, 11, 12, 0);

// we'll store our filtered results in this list
List<Integer> filtered = new ArrayList<>();

// filter the list and allow only numbers that are less than 10
for (Integer number : numbers) {
    if (number < 10) {
        filtered.add(number);
    }
}

// print out the filtered list
for (Integer number : filtered) {
    System.out.println(number);
}

Output:

1
2
3
0

In this example our filter is inside the first for loop, where we check for each number in our list whether it passes or not the criteria we have, in our case, we only allow numbers that are less than 10.

Changing the filter

Now, what happens if we’re asked to implement multiple filters? For example, what if we also need to filter the list to allow only numbers greater than 5?

We could of course, change our filter to be:

//...
for (Integer number : numbers) {
    if (number > 5) {
        filtered.add(number);
    }
}
//...

However, this isn’t very generic and depending the case it might not be what we want to do.

For example, what happens if we need to filter using logic that we don’t control? Like in the case where we provide a code library and the users of our library want to define how to filter the data.

This now sounds like we’re defining a contract between us and others, and of course, the easiest approach to do this is to provide an interface that defines this contract of what a Filter is.

In our case, a filter receives an element (in our example an Integer) and it should return true if the element should be included, and false otherwise. This interface can be modelled like this:

public interface Filter<E> {
    boolean filter(E element);
}

Greater than 5 filter

Now, for the second filter mentioned above, we can define an implementation of our Filter class that filters numbers and only allows numbers greater than 5, as such:

public class GreaterThanFiveFilter implements Filter<Integer> {

    @Override
    public boolean filter(Integer element) {
        if (element > 5) {
            return true;
        }
        return false;
    }

}

Filtering numbers using a Filter

Once we have the implementation of Filter that we want to use, we can change our original code to use it, for example:

In the example code run the JavaListExampleWithFilterApp
public static void main(String[] args) {
    // our input list of numbers
    List<Integer> numbers = Arrays.asList(1, 2, 3, 10, 11, 12, 0);

    // get the filter we want to use
    Filter<Integer> filterToUse = new GreaterThanFiveFilter();

    // call our new `filterNumbers` static method to do the filtering
    List<Integer> filtered = filterNumbers(numbers, filterToUse);

    // print out the filtered list
    for (Integer number : filtered) {
        System.out.println(number);
    }
}

private static List<Integer> filterNumbers(List<Integer> numbers, Filter<Integer> filterToUse) {
    List<Integer> filtered = new ArrayList<>();
    // filter the list using the provided `filterToUse`
    for (Integer number : numbers) {
        if (filterToUse.filter(number)) {
            filtered.add(number);
        }
    }
    return filtered;
}

Output:

10
11
12

The good and the bad

The good thing with our new approach is that we now have a new method called filterNumbers that receives the list of numbers we want to filter and it can also receive any implementation of our Filter interface to perform the filtering.

In this sense, we can provide any type of filter we want (like, finding numbers lower than, greater than or equal to a given number, or anything we want really).

The bad thing is that we need to create a whole new class per filter we want to apply. This is very verbose, for example, we already have a GreaterThanFiveFilter class, and if we want to implement other filters, like in the first example above, we’d need to create more classes.

This is one place where anonymous classes and, more recently, lambdas can help as we’ll see in the next 2 sections.

This of course isn’t really a major problem, but it isn’t great either. Our example is very basic and only meant to introduce the concepts in the next sections. This is to highlight the issue we’re trying to solve.