Functional Programming With Java: What’s in the Box
In the previous part, we learned about Java’s functional capabilities. This time, we’ll go over the functional interfaces which are included in the JDK since Java 8’s introduction of lambdas.
The package java.util.function
` provides 43 functional interfaces, containing all the building blocks needed to create quite complex functional code.
Table of Contents
The Big Four
The four functional interfaces that we’ll likely use the most are:
T Supplier<T>#get()
Takes no arguments, but returns an object. A method reference to a simple POJO-Getter qualifies as aSupplier
.void Consumer<T>#accept(T t)
Takes a single argument and doesn’t return anything. Every POJO-Setter qualifies as aConsumer
.R Function<T, R>#apply(T t)
Takes a single argument and returns an object.boolean Predicate<T>#test(T t)
A specialized function that accepts an object and returns aboolean
primitive.
With just these four alone we can do a lot of functional programming:
Function Arity
Lambdas often work with the same type as argument(s) and return type.
With Java being a verbose language, we don’t want to write the parameterized types all the time. To avoid this, Java provides more specialized interfaces with more straightforward generic signatures:
Be aware that instead of using the ...Operator<T>
as arguments in a method, we should prefer using its super-interface instead, to make the method more flexible.
Primitive Types
Until Project Valhalla and generic specialization (JEP-218) will be available, we can’t use primitives as parameterized types.
Even though autoboxing works “automagically” at compile-time, the added overhead in memory footprint and possible performance implications can offset the initial goal of more concise and performant code.
Just like the primitive wrappers, Java provides multiple specialized functional interfaces for primitive types, too.
Numeric primitives
The numeric primitives int
, long
, and double
have their own specialized functional interfaces. Here are the ones for int
:
IntConsumer
ObjIntConsumer<T>
ABiConsumer
-like interface, accepting an object-type and anint
argument. It’s supposed to have side effects by affecting the consumed object argument.IntSupplier
IntPredicate
IntFunction<R>
IntUnaryOperator
IntBinaryOperator
ToIntFunction<T>
Accepts an object-type argument and produces anint
value.ToIntBiFunction<T,U>
Accepts two object-type arguments and produces anint
value.
All these interfaces exist for the other two types long
and double
with the corresponding type names. The fact that some of the primitive types are missing isn’t too bad, they can easily be replaced by the existing types through casting.
Conversion functions
The previous functional interfaces are either accepting or returning primitive types. But sometimes we want to accept and return a primitive. Java’s got you covered in this case, too:
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
Boolean
The boolean
primitive doesn’t get as much love as the numeric primitive types. Only a single functional interface is explicitly prefixed:
But Predicate<T>
and its primitive counterparts can be seen as specialized functional interfaces returning boolean
primitives.
Default Methods on Functional Interfaces
For creating quite complex expressions, or simplifying lambda creation, many functional interfaces provided by the JDK have default methods. They are often designed as fluent interfaces.
Here are two examples, check out the functional interfaces mentioned in this article to find more.
Comparator
A simple functional interface for comparing two objects of the same type. Let’s look at a simple example:
Thanks to the default method Comparator.comparing(Function<T,U>)
we can simplify the code:
Way simpler, and it can be nicely inlined into Stream#sorted(Comparator<? super T> comparator)
.
Predicate
Another functional interface with default methods is the already mentioned Predicate<T>
. It provides everything, the fundamental building blocks for building a multi-criteria predicate chain:
Conclusion
The JDK provides us with a lot of different functional interfaces. Due to Java’s two-fold type system differentiating between object-types and primitives, there’s a need for specialized interfaces for primitives if we want to handle them efficiently.
The fluent interfaces of many functional interfaces provide many parts to build complex expressions. Especially in combination with the Stream API and method references, we can craft quite concise and readable code.
Resources
java.util.function
package (Oracle)- Functional interfaces (Oracle)
- Function interfaces in Java 8 (Baeldung)