3.8 Higher-Order Functions
In a functional programming language, functions are first-class citizens. Just like you can pass numbers to methods and have methods that produce numbers, you can have arguments and return values that are functions. Functions that process or return functions are called higher-order functions. This sounds abstract, but it is very useful in practice. Java is not quite a functional language because it uses functional interfaces, but the principle is the same. In the following sections, we will look at some examples and examine the higher-order functions in the Comparator interface.
3.8.1 Methods that Return Functions
Suppose sometimes we want to sort an array of strings in ascending order and other times in descending order. We can make a method that produces the correct comparator:
public static Comparator<String> compareInDirecton(int direction) {
return (x, y) -> direction * x.compareTo(y);
}
The call compareInDirection(1) yields an ascending comparator, and the call compareInDirection(-1) a descending comparator.
The result can be passed to another method (such as Arrays.sort) that expects such an interface.
Arrays.sort(friends, compareInDirection(-1));
In general, don’t be shy to write methods that produce functions (or, technically, instances of classes that implement a functional interface). This is useful to generate custom functions that you pass to methods with functional interfaces.
3.8.2 Methods That Modify Functions
In the preceding section, you saw a method that yields an increasing or decreasing string comparator. We can generalize this idea by reversing any comparator:
public static Comparator<String> reverse(Comparator<String> comp) {
return (x, y) -> comp.compare(y, x);
}
This method operates on functions. It receives a function and returns a modified function. To get case-insensitive descending order, use
reverse(String::compareToIgnoreCase)
3.8.3 Comparator Methods
The Comparator interface has a number of useful static methods that are higher-order functions generating comparators.
The comparing method takes a “key extractor” function that maps a type T to a comparable type (such as String). The function is applied to the objects to be compared, and the comparison is then made on the returned keys. For example, suppose a Person class has a method getLastName. Then you can sort an array of Person objects by last name like this:
Arrays.sort(people, Comparator.comparing(Person::getLastName));
You can chain comparators with the thenComparing method to break ties. For example, sort an array of people by last name, then use the first name for people with the same last name:
Arrays.sort(people, Comparator
.comparing(Person::getLastName)
.thenComparing(Person::getFirstName));
There are a few variations of these methods. You can specify a comparator to be used for the keys that the comparing and thenComparing methods extract. For example, here we sort people by the length of their names:
Arrays.sort(people, Comparator.comparing(Person::getLastName,
(s, t) -> s.length() - t.length()));
Moreover, both the comparing and thenComparing methods have variants that avoid boxing of int, long, or double values. An easier way of sorting by name length would be
Arrays.sort(people, Comparator.comparingInt(p -> p.getLastName().length()));
If your key function can return null, you will like the nullsFirst and nullsLast adapters. These static methods take an existing comparator and modify it so that it doesn’t throw an exception when encountering null values but ranks them as smaller or larger than regular values. For example, suppose getMiddleName returns a null when a person has no middle name. Then you can use Comparator.comparing(Person::getMiddleName(), Comparator.nullsFirst(...)).
The nullsFirst method needs a comparator—in this case, one that compares two strings. The naturalOrder method makes a comparator for any class implementing Comparable. Here is the complete call for sorting by potentially null middle names. I use a static import of java.util.Comparator.* to make the expression more legible. Note that the type for naturalOrder is inferred.
Arrays.sort(people, comparing(Person::getMiddleName,
nullsFirst(naturalOrder())));
The static reverseOrder method gives the reverse of the natural order.