Skip to main content
Logo image

Problem Solving with Algorithms and Data Structures using Java: The Interactive Edition

Section D.2 Higher Order Functions

When processing a collection of data, we often want to keep only certain items from that collection. For example, we might want to keep only negative values and discard non-negative values when going through a list of debits and credits. Or, we might want to keep only strings that have more than ten characters when selecting a password. This process is also called filtering; we are “filtering out” the values we don’t want.
Consider the following program that implements three methods that take an ArrayList and create a new ArrayList that retains only specific elements of the original list. The first method will keep only the even elements. The second method keeps only the negative elements, and the third method keeps only the two-digit numbers from the original list:
Listing D.2.1.
That’s a lot of duplicated code! Wouldn’t it be nice if there were some way to have a program like this, where we pass the “testing” code as a parameter (and also use a generic type so that we can use it on an ArrayList of any type):
public static <T> ArrayList<t> keep(ArrayList<t> data,
  «booleanMethod») {
    ArrayList<T> result = new ArrayList<T>();

    for (T item: data) {
        if («booleanMethod»(item)) {
            result.add(item);
        }
    }
    return result;
}

//... and then, in the main() method


ArrayList<Integer> evens = keep(numbers,
    boolean isEven(n) { return n % 2 = 0; });

ArrayList<Integer> negative = keep(numbers,
    boolean isNegative(n) {return n < 0; });
Listing D.2.2.
Spoiler alert: Yes, it would be nice. And that’s what we’re going to examine in this section: Higher Order Functions, which give us the ability to pass methods as parameters to other methods. Higher order functions are a core concept in functional programming.

Note D.2.3.

The preceding pseudo-code is just an example. We’ll use different notation when we write it in a real Java program, but the idea is the same: we’re passing a method as a parameter to another method.

Subsection D.2.1 Predicates

We will use the Predicate class to implement our higher order function. A predicate is another name for a boolean method. (This isn’t like a predicate in an English sentence; it means something is conditional, or predicated on something else.)
Let’s start with the code for our new keep method. It will have two parameters: an ArrayList<T> and a Predicate<T>, which will be our boolean method. We’ll have to import java.util.function.Predicate. The Predicate class uses a generic to specify what kind of value is being tested, and it contains a test method that invokes the method passed to it:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Predicate;

public class KeepItemsFunctional {
    public static <T> ArrayList<T> keep(ArrayList<T> data,
      Predicate<T> p) {

        ArrayList<T> result = new ArrayList<T>();

        for (T item: data) {
            if (p.test(item)) {
                result.add(item);
            }
        }
        return result;
    }
Listing D.2.4.

Note D.2.5.

Although we usually want meaningful variable names, the predicate can be virtually anything, so there is really no good descriptive name for it. Thus we’ll go with p. We could also have used pred to be a bit more specific; use whichever name you prefer in your code.

Subsection D.2.2 Lambda Expressions

Now that we have a method that requires a Predicate<T>, let’s look at the main method. This is where we will use lambda expressions to specify the code we want to pass to a method.
In essence, a lambda expression is a method without a name. There are many different ways to write a lambda expression, but we’ll stick with one format for the sake of simplicity and not go into all the possible shortcuts. Let’s look at a boolean method for determining whether a number is even or not:
boolean isEven(Integer n) {
    return n % 2 == 0;
}
Listing D.2.6.
To convert this method into a lambda expression, we get rid of the return type and the name, and insert an arrow operator -> before the opening brace:
(Integer n) -> {
    return n % 2 == 0;
}
Listing D.2.7.
We can now put that into our main method:
    public static void main(String[] args) {
        Integer[] data = {10, 47, -311, 66, 254, -99, 140};
        ArrayList<Integer> numbers = new ArrayList<Integer>(Arrays.asList(data));

        ArrayList<Integer> evens = keep(numbers,
            (Integer n) -> {
                return n % 2 == 0;
            });
        System.out.println("Even elements: " + evens);
    }
}
Listing D.2.8.
You may have noticed that it’s a bit tricky to keep track of the braces and parentheses in the call to keep in lines 5-8. Let’s do the lambda expression for testing negative numbers as a separate entity. We’ll have to assign it to a Predicate<Integer> variable, because that is what keep expects. Add this code before the end of main:
Predicate<Integer> isNegative = (Integer n) -> {
    return (n < 0);
};
ArrayList<Integer> negatives = keep(numbers, isNegative);
System.out.println("Negative elements: " + negatives);
Listing D.2.9.
And we’ll do the same for our lambda expression to test if the number is a two-digit number or not:
Predicate<Integer> isTwoDigits = (Integer n) -> {
    return (Math.abs(n) > 9 && Math.abs(n) < 100);
};
ArrayList<Integer> twoDigits = keep(numbers, isTwoDigits);
System.out.println("Two-digit elements: " + twoDigits);
Listing D.2.10.
Here’s the entire program:
Listing D.2.11.
You have attempted of activities on this page.