Top 5 functional Java interfaces
Many developers get intimidated when they look at all the new APIs that accept lambda expressions as arguments, and wonder how they’ll ever figure out which Java function to use and what the proper syntax will be. There are over 40 functional interfaces listed in the java.util.functions package, which can make the prospect of functional programming in Java a somewhat daunting task.
However, on close inspection, you’ll notice that there are only five or six actual functions you need to get comfortable with, namely:
- Function
- Consumer
- Supplier
- Predicate
- and the UnaryOpeartor
With a basic understanding of these functional interfaces, you can easily master the other 35 or so functions because the remainder simply build on these five concepts.
Just look at the various functional interfaces listed in the java.util.functions package. A quick sampling of names includes:
- BiConsumer, IntConsumer, LongConsumer
- BiFunction, IntFunction, IntToDoubleFunction
- BiPredicate, IntPredicate, LongPredicate
- BooleanSupplier, LongSupplier, DoubleSupplier
- LongUnaryOperator, DoubleUnaryOperator, IntUnaryOperator
As you can see, the various Java functional interfaces simply provide a somewhat different spin on the core functionality described in the Function, Consumer, Supplier, Predicate and UnaryOperator functional interfaces. If you master those five components, you’ll have no problem figuring out how to use lambda expressions in all the spots in which the newer Java APIs require them and be well on your way to mastering functional programming in Java.
Lambdas and the Function interface
The goal of the Function interface is to take a value from the program, perform some type of calculation or operation on that value and then return a new value. Here’s an example of how a Function and lambda expression work together:
Function<Integer, String> verboseLambda = (Integer x)-> { return Integer.toString(x*x); }; System.out.println(verboseLambda.apply(5));
The return keyword on the right-hand side of the lambda expression is the key to the Function interface.
The Consumer interface function
In contrast to the Function interface, the Consumer interface doesn’t return a value. The Consumer interface is passed a value, performs some type of operation or calculation on that value and then terminates without returning anything. Here is a Consumer interface and lambda expression example:
Consumer<Long> conciseLambda = (Integer t) -> System.out.println(t*t); conciseLambda.accept(new Long(10));
Again, the difference between the Consumer and Function interface is the fact that the return keyword is absent on the right-hand side of the lambda expression.
The Supplier interface function
In the previous two examples, an instance of the Integer class was passed to both the Function and the Consumer. The Supplier interface, however, isn’t passed anything. It simply generates a value based solely on its internal logic and then returns that value. Here is a Supplier interface and lambda expression example:
Supplier<Integer> rds = () -> new Random().nextInt(10);
The Predicate and Streams API
Boolean logic forms the foundation of all computer programs, so it’s no surprise to find out that there is a functional interface dedicates to true or false values. Like the Function and Supplier interface, the Predicate interface is passed a value. The key difference, however, is that when the Predicate interface runs, a true or false value must be returned. Here is a lambda expression and Predicate example:
Predicate<Integer> lambdaPredicate = (Integer x) -> (x % 2 == 0);
The functional Predicate interface gets used extensively by the Java 8 Streams API. Any developer who wants to master functional programming in Java will need to be comfortable with the Predicate interface and will make manipulating collection classes with lambda expressions extremely easy.
The UnaryOperator function
Like the Function interface, the UnaryOperator is passed a value and returns a value. However, the distinction between the UnaryOperator and the Function is that the UnaryOperator must return the exact same type of object that it’s passed. For example, if a “Person” object is passed to the UnaryOperator, a “Person” object must be passed back. This restriction doesn’t apply to the Function interface. Here’s an example of the UnaryOperator in action using a String as the unary type:
UnaryOperator<String> extensionAdder = (String text) -> { return text + ".txt";} ;
The move into functional programming in Java can be intimidating. There were a lot of new interfaces introduced in Java 8 to make working with Streams and lambda expressions possible. But, most of those functions are simply just derivations of these five basic interfaces.
If you can master the Supplier, Consumer, Function, Predicate and UnaryOperator interfaces, the entire functional programming in Java paradigm will become much, much easier to master.