General-purpose functional interfaces for use with lambdas
We have seen that we can define a functional interface as a "blueprint" for a Java lambda expression.
The functional interface contains a single method defining the input parameters and return type of the lambda. This is quite a neat solution,
as it means that many methods that previously required an interface instance to be passed into them will now automatically work
with lambda expressions. In our introduction to lambdas, we saw examples with Comparator
and Runnable
.
That's all very well for backwards compatibility, but what about new methods where in reality, we are only ever intending to pass in a lambda expression?
If our lambda expression happens to match the arguments of a pre-existing interface such as Runnable
or Comparator
, we
can define our method to take, say, a Comparator
, even though we will always be passing in a lambda expression.
And for methods where we want to pass in a lambda with some other combination of arguments, we can define a functional interface accordingly.
But if we are making frequent use of lambdas, it will soon become inconvenient to have to declare a functional interface for every distinct
combination of lambvda arguments that we happen to use. And some of these will be quite common. For example, a common case might be to define
methods that take a "filter" expression, i.e. a lambda that takes a single argument and returns a boolean. Another common case would be
a "getter": a lambda that defines how to return some field from an object, such as our "get description" example on the previous page.
To alleviate the need to define new interfaces for common combinations of lambda parameters, Java 8 comes with a set of
general-purpose functional interfaces.
For example, the Function
interface is a general-purpose functional interface for lambda expressions that take one object as
their input argument and return an object. Ignoring some details, it is essentially defined as follows:
public interface Function<T, R> {
R apply(T t);
}
As you can see, Function
is a generic interface, meaning that we can parameterise it with the specific classes of objects in question. Our DescriptionGetter
interface from the previous funtional interface example takes a DataItem
and returns a String
. Therefore, it is effectively equivalent to Function<DataItem, String> and we can re-implement our previous example as follows:
public static List<String> getItemDescriptions(List<? extends DataItem> items, Function<DataItem, String> getter) {
List<String> ret = new ArrayList(items.size());
for (DataItem item : items) {
String desc = getter.apply(item);
ret.add(desc);
}
return ret;
}
Functional interfaces are provided for a range of combinations of zero, one or two input parameters and various return types. Another interface that you will commonly use is
Predicate. This provides a template for a lambda expression that takes an object and returns a boolean. It is therefore used in cases where the expression defines a filter or condition.
The following table summarises the various functional interfaces that are defined in the standard Java API:
Input parameters | Return |
None | Object | boolean | int | long |
None | --- | Supplier | BooleanSupplier | IntSupplier |
Object | Consumer | Function | Predicate | ToIntFunction |
Object, Object | BiConsumer | BiFunction | BiPredicate | ToIntBiFunction |
int | IntConsumer | IntFunction | IntPredicate | IntOperator |
long | LongConsumer | LongFunction | LongPredicate | LongToIntFunction | LongOperator |
As you will glean from the table, the generic functional interfaces genreally follow a standard naming convention, distinguishing between:
- Consumers, which have no return value and may have a "side effect"
- Suppliers, which have no input and simply provide a value (in effect, these define producers in the producer-consumer model)
- Predicates, which have a boolean return type
- Operators, which return a value of the same type as their input (and which have input values of the same type if there are multiple inputs)
- Functions, which have arbitrary input and return types
The prefix Bi-
is used to indicate two input parameters. The input or return type is generally considered to be an object unless a specific type is included in the name (in which case it generally refers to a primitive type). Where the type is included, it is prefixed with To-
to indicate a return type rather than an argument type. In places where Int or Long appear in the interfaces listed above, Double is generally also possible.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.