Last week I wrote about functional interfaces and how to use them as targets for lambda expressions. I showed you how we create a variable of a functional interface type and assign a lambda expression to it. Today I want to help you make sense of the set of standard functional interfaces.
Stream processing is a big part of Java 8 and above. Stream processing makes it easy to process large data sets without writing code to iterate through collections by ourselves. Stream processing uses lambda expressions and functional interfaces extensively. These standard functional interfaces are packaged in the java.util.function
package.
At first, the set of standard functional interfaces looks daunting. There are 43 of them! And they have complex and confusing names like ToLongBiFunction
, ObjDoubleConsumer
and LongBinaryOperator
.
Never fear, help is here!
The 5 groups of standard functional interfaces
The secret to understanding these functional interfaces is that they consist of five groups. The grouping tells you what the functional interface does:
- A
Consumer
consumes an input and returnsvoid
. - A
Supplier
produces values. - A
Predicate
does a test and returns aboolean
. - A
Function
is a function that processes some arguments and returns a result. - An
Operator
is similar to aFunction
: it processes some arguments and returns a result. AnOperator
is a specialization ofFunction
where the arguments and return value are of the same type.
Hints to help you find the right functional interface
When looking for a suitable interface, or trying to understand what is needed by some stream processing method, all you need to do is look at the last word in the interface name. It will be one of the five groups and will tell you what it does (consume, produce, test or run a function).
The first words in the interface name will tell you what type of data it works with (Boolean
, Double
, Int
, Long
or Obj
for objects), and whether it takes one or two arguments (Unary
, Bi
or Binary
).
That’s it! It’s as simple as that.
Example using Consumer interfaces
Let’s look at the Consumer
interfaces as an example of how you interpret the names.
The Consumer
interfaces all returnvoid
. They can either take one parameter (unary) or two parameters (binary). They are also specialised as either taking generic parameters, primitive parameters or a combination of a generic and a primitive.
Name | Name / type of arguments |
---|---|
Consumer |
One generic object argument |
BiConsumer |
Two generic object arguments |
IntConsumer |
One int argument |
LongConsumer |
One long argument |
DoubleConsumer |
One double argument |
ObjIntConsumer |
One generic and one int argument |
ObjLongConsumer |
One generic and one long argument |
ObjDoubleConsumer |
One generic and one double argument |
Naming conventions for the functional interfaces
The functional interfaces follow an extensible naming convention.
- The last word in the interface name tells you the basic operation. This will either be
Consumer
,Supplier
,Predicate
,Function
orOperator
. - If you see
Bi
orBinary
, then you know the functional interface takes two parameters. - If it says
Unary
, it takes one parameter. - If you see
Int
,Long
, orDouble
, then you know that it’s a primitive specialization. - If you see
Int
,Long
, orDouble
andObj
, then you know that it takes a primitive and a generic as parameters. - If you see
To
in the name, then you know that it converts from one type to another.
Why do we need them?
We can create our own functional interfaces to use as targets for lambdas. Or we can use a suitable interface from the java.util.function
package.
A best practice is always to use one of the standard functional interfaces, if it fits your purpose.