Since Java 8, a lot of boilerplate code can be replaced with lambda expressions in our codebase. I really want to put out an article about Streams in Java, but since that carries real value only if we combine them with lambda expressions, I want to write about some ideas about playing around with lambda expressions first.
Functional Interfaces in Java
A functional interface is an interface with a single abstract method, also known as SAM (Single Abstract Method) types. The concept was introduced in JDK 8, but there were interfaces prior to JDK 8 that complied with that definition. For example, Comparator
has only one method:compare()
.
We can create our own as:
@FunctionalInterface interface Calculator<T,R> { R calculate(T a, T b); }
Here, the @FunctionalInterface
annotation is checked by the compiler. So, if the interface does not contain exactly one abstract method, there is a compiler error.
It is not necessary to use this annotation when providing a type for a lambda expression, but, like other annotations (@Override
, for example), it is a best practice to use it because it tells the compiler to check that it would work — otherwise, it will be overlooked until runtime.
Now, What Is a Lambda Expression?
A lambda expression is an inline implementation of a functional interface, eliminating the need of an anonymous class.
A lambda expression has three parts:
parameters [zero or more] -> code block [if more than one statement, enclosed in curly braces { . . . } ] [may contain free variables; values for these supplied by local or instance vbles]
For example:
(a,b) -> a + b; // given two numbers a and b, return another number with their summation. (String str) -> System.out.println(str); // prints the given string into console
The equivalent of the first lambda expression is:
new Calculator<Integer, Integer>() { @Override public Integer calculate(Integer a, Integer b) { return a + b; } };
You can see how lambda expressions help to get rid of the boilerplate code.
Naming Lambda Expressions
Every object in Java has a type; the same is true of lambda expressions. The type of a lambda expression is any functional interface for which the lambda expression is an implementation.
Naming a lambda expression is done by using an appropriate functional interface as its type, like naming any other object. For example:
Calculator<Integer, Integer> adder = (a,b) -> a + b;
The name of this lambda expression is adder
of type Calculator
.
Note: I am omitting the parameter type because if parameter types can be inferred, they can be omitted. So, the above expression with the parameter type would be:
Calculator<Integer, Integer> adder = (Integer a,Integer b) -> a + b;
Using Lambda Expressions
You've seen how inner class approximations can be replaced by lambda expressions, which capture their essential functional nature: Arguments mapped to outputs. Hence, the functions are now first-class citizens. So:
1. A lambda expression can be used as a regular class object to call the method. For example, adder
can be used to call the calculate
method as:
Integer sum = adder.calculate(3,4); // sum = 7
2. Lambda expressions can be passed as an argument and used to evaluate expressions as:
// ... printSum(adder); // ... static void printSum(Calculator cal) { System.out.println(cal.calculate(4,5)); }
3. Lambda expressions can be returned as a return object:
// ... Calculator<Integer, Integer> multiplier = getCalculatorFunc(); Integer result = multiplier.calculate(6,9)); // 54 // ... static Calculator getCalculatorFunc() { Calculator<Integer, Integer> multiplier = (a, b) -> a * b; return multiplier; }
Conclusion
Now, you can see how it is possible to reap many of the benefits of Functional Programming while maintaining the OO essence of the Java language as a whole. Lambda expressions are the fundamentals for functional programming and writing code concisely with Streams, which I want to continue in the next post. Until then, happy coding!
The source code for the examples presented above is available on GitHub.