Java Functional Programming : 2. Lambda Expressions & Java Functional Interfaces
This is the multi part series on Java Functional Programming
2. Lambda Expressions & Java Functional Interfaces
Since this topic could be new to most of the programmers we would
address it in an iterative way. We shall throw in the concepts without much
detail, and make the programmer understand one aspect of it. Then when that is
clear we shall come back and then begin with another aspect. This way in many
iterations and repeating different aspects the programmer would get a fair
knowledge of the subject. IT is advised that the programmer practice and refers
the Java API along the way. No amount of reading can replace the virtues of a
first-hand programming.
In programming, a Lambda Function
(or Lambda Expression or Anonymous Function) is a function definition which is
not bound to an identifier. They are passed as a parameter to higher order
function and can be returned back from a function. Java did not have a
framework to define just the function and pass them along as a parameter or as
a return value. Java does have a concept of anonymous classes that can be used
instead. Anonymous classes are defining the class and using it once and then
discard it. It can be passed as a parameter too (more on this is taken in a
separate topic, earlier).
Note that java is inherently not
a functional programming language and so in its core it does not have a
mechanism to pass method references. Java implements this referencing of a
method using an indirect (and a little non-intuitive) way. Java has interfaces
which contain abstract methods. Java can therefore use these interfaces to
reference the lambda expressions that indirectly reference the abstract methods,
for which the expressions can be defined later. This however poses a problem,
when we have more than one abstract method defined in the interface. Since the
lambda expressions can be referenced only by the interface, there is a problem
of uncertainty, when more than one abstract method is declared. The java
program does not know which abstract method should be called. Java circumvents
this problem by making it mandatory that such interfaces which are to be used
as lambda expressions must have ‘one and
only one’ abstract method. They can have any number of default and static
methods (came with Java 8) though. Such interfaces are also known as “functional
interface”. Any interface which has one abstract method qualifies for a
functional interface. Java also provides for an annotation @FunctionalInterface
which can be annotated to the interface to do a compile time check. The
annotation is not mandatory for defining a “functional interface” but
serves as a marker to perform a compile time check.
The programmers must define the “function
interface” and then define the concrete functionalities when using
them. For all practical purposes, a functional interface is just like any other
interface, but ideally, they should not be implemented, rather used strictly as
an abstraction of a function it defines. If you annotate an interface as a @FunctionalInterface,
implementing it as an interface defeats its purpose and is not a good
programming construction, even though the java programming language allows it. To
understand functional interfaces further, let’s have look at the code.
This is a declaration of a functional interface – the @FunctionalInterface annotation
is not mandatory but recommended.
@FunctionalInterface
public interface IntMathOperation {
public int operation(int int1, int int2);
}
|
IntMathOperation
imoAdd = (a, b) -> a + b;
IntMathOperation
imoSub = (a, b) -> a - b;
IntMathOperation
imoMul = (a, b) -> a * b;
System.out.println("Add
4, 5 = " + imoAdd.operation(4, 5));
System.out.println("Subtract
4, 5 = " + imoSub.operation(4, 5));
System.out.println("Multiply 4, 5 = " + imoMul.operation(4, 5));
|
Looking at the code above, we are
declaring the method body in the first 3 lines. We have abstracted the function
itself and taken the functionality of the method “int operation( int int1, int int2)”
outside. The first line declares the addition operation, the second subtraction
and the third multiplication. Only one functional
interface can be used to declare multiple functions. In this case the same
interface is used to perform 3 functions – addition, substraction and multiplication.
The values ( a, b ) in the braces correspond to the parameter of the
method declaration “int operation( int int1, int int2)”. The ciompiler figures out that
a,
b are of type int, so no specific declaration is
needed. The arrow ( -> ) is the
assignment of the abstract function to the interface. The rightmost section is
what the action the lambda should be performing. Once the lambda declaration
has been done we can reference it with the identifier (imoAdd, imoSub,
imoMul
in this case), and can be used to invoke the method operation( ). The invoked function would now behave
as directed by the lambda expression. The last three lines which invoke the
three different types of lambda expression would present the following output:
Add 4, 5 = 9
Subtract 4, 5 = -1
Multiply
4, 5 = 20
I think a question would have
crossed your mind by now – Why doesn’t java allow more than one abstract
method in its functional interface?
We did touch upon it earlier. It’s simple and obvious. Java is not
inherently a functional programming language, so it has to work around with features
it currently has, with minimal changes to the language constructs, and supporting
backward compatibility. How Java does it is by attaching the lambda expression
to the interface (and not to a method reference). If you notice, the reference
for the lambda is the interface itself, and not the methods.
IntMathOperation
imoAdd = (a, b) -> a + b;
Had there been more than one
abstract method in the interface this syntax would not have been possible. Thus,
Java disallows more than one abstract method in a functional interface.
Proceeding to the syntax, the
above is not the only way to define the lambda expressions. Not all methods
would be as simple as a + b. In case the method is complex
you can conveniently put the methods in the curly braces and then define the
method inside it. All the below are valid lambda expressions and you must
choose according to your need. Do not choose the simplest construct. Choose
instead what would be more readable to the others reviewing your code.
IntMathOperation
imoAdd1 = (a, b) -> a + b;
IntMathOperation
imoAdd2 = (a, b) -> (a + b);
IntMathOperation
imoAdd3 = (int a, int b) -> a + b;
IntMathOperation
imoAdd4 = (a, b) -> { return a + b; };
IntMathOperation imoAdd5 = (int a, int b) -> { return a + b; };
|
Personally, I would prefer the 3rd
and 5th declaration because it exactly tells the programmer what the
‘type’ of parameters ‘a’ and ‘b’ is, and not rely on interface’s declaration
for clarity. Remember the functional interface would usually be defined in a
separate file. For large methods, this would be very informative. It is not
necessary to first declare the functional interface and then pass it as a
method argument. Though not mandatory it is also advisable to annotate the functional
interfaces with the annotation @FunctionalInterface to catch errors
in the interface itself and make it self-evident. Java also annotates many of
its exiting interfaces such as java.lang.Runnable,
java.util.Comparator<T>, etc. which can be used directly as a
lambda in the parameter. (Note again, annotating is just marking, it could
still be used as a lambda function even if it wouldn’t have been annotated).
<< Prev (Introduction) | Next (The java.util.function Library) >>
Comments
Post a Comment