Java 8 Lambda Expressions
Bhaskar S | 10/12/2014 |
Lambda expressions are nothing more than anonymous functions.
So whats all the buzz around it ??? Hang in there - will illustrate the power of Lambda expressions shortly.
To execute any task in a thread, one usually implements the task as a Runnable.
The following code implements a simple task using Runnable:
The following code executes the simple task:
Executing the program from Listing.2 will generate the following output:
Hello Java 8 !!!
In the above example, we have two separate classes - one for the task and the other for executing the task. As more tasks are added, more task classes will be created and this simple application will get cumbersome to maintain.
The following code improves upon the previous example by eliminating the task class file by using an inner task class:
In the above example, as more inner task classes are added, this simple application will get large and hard to maintain.
The following code improves upon the previous example further by eliminating the inner task class by using an anonymous task class:
Can we further simplify the above example ???
The answer is YES !!!
This is where the Lambda expressions come into play.
The following code improves upon the previous example further by eliminating the anonymous task class by using a lambda expression:
As can be seen from the code in Listing.5, the code using Lambda expressions is much more concise and easier to read and maintain.
In the above example, the piece of code new Thread(() -> System.out.println("Hooray Java 8 !!!") represents the Lambda expression.
In other words, a Lambda expression is an anonymous block of code that can be passed as an argument to another class or method for execution.
Lambda expressions can be used only in situations where the type is a functional interface. A functional interface is an interface that contains a single abstract method, as in the case of Runnable.
The general syntax for defining a Lambda expression is as follows:
(parameters) -> {block of code}
where:
parameters can be zero or more arguments names with no type indication. For empty (zero) arguments, use ().
Example:
Runnable r = () -> System.out.println("Empty Arguments");
-> is the Lambda operator (or arrow operator)
block of code is the code to be executed. Use curly braces {} only when there are multiple statements to execute.
Example:
Runnable r = () -> { System.out.println("Multiple"); System.out.println("Statements"); }
Let us look at another example - sort a list of strings in alphabetical order.
The following code uses the Comparator functional interface (an interface that has only one method):
Executing the program from Listing.6 will generate the following output:
Names <before>: [zero, three, five, seven, nine] Names <after>: [five, nine, seven, three, zero]
The following code is a modified version of Listing.6 using Lambda expression that produces the same result as in Output.2:
Since a Lambda expression (sa, sb) -> sa.compareTo(sb) is passed as the second argument to the Collections.sort() method, the compiler is able to determine the functional interface as Comparator and that the parameters sa and sb are of type String.
Java 8 introduces a default method called sort(Comparator<T>) in the List collection interface.
The following code is a modified version of Listing.7 using the sort() method from the List collection that produces the same result as in Output.2:
Until now, in the examples demonstrated, we used Java's functional interfaces such as Runnable and Comparator. Nothing is preventing us from implementing our own functional interfaces.
Let us look at a example demonstrating custom functional interfaces.
The following code defines a custom functional interface to test for even numbers:
The use of the annotation @FunctionalInterface is optional and is for informational purposes only. The use of the annotation indicates the intended purpose of the interface and makes the code more readable.
The following code uses our custom EvenTester functional interface, creates a Lambda expression to check for even numbers, and passes it to a method for displaying the even numbers:
Executing the program from Listing.10 will generate the following output:
Even #s: 0 2 4 6 8
As indicated earlier (when we illustrated the syntax for Lambda expression), the parameters for Lambda expressions are not typed. The question then arises - how can Lambda expressions leverage generic types ???
The answer - the functional interfaces associated with Lambda expressions can use generic types and hence implicitly leveraged by Lambda expressions.
Let us illustrate this with an example.
The following functional interface defines a generic number tester:
The following code uses our custom NumberTester functional interface to display numbers divisible by 4:
Executing the program from Listing.12 will generate the following output:
Divisible by 4 #s: 20 40 60 80
As can be seen from the above example, Lambda expressions are able to work very well with generic types.
Java 8 introduces additional generic functional interfaces in the package java.util.function.
In the above example, we can get rid of our custom functional interface and use the Java 8 built-in functional interface BiPredicate<T, U>.
This built-in functional interface represents a predicate that returns a boolean value and takes two generic arguments. The functional method defined is test(T arg1, U arg2).
The following code uses the built-in BiPredicate functional interface to display numbers divisible by 4:
There will be situations when one might want to reuse method(s) (static or instance) from existing class(es) as implementations for functional interfaces. In such scenarios, we will need a way to refer to those method(s) without executing them. This is where Method References come into play.
Method references are related to Lambda expressions since the target type of the method for re-use must be compatible with the functional interface type in the context where it is used.
Let us illustrate this concept with an example.
Assume we had a simple class for displaying text (as shown below) and is used by existing applications. It defines two methods - one is a static method and the other is an instance method.
The following is a simple program that displays the contents of a list using the class from Listing.14 above:
Executing the program from Listing.15 will generate the following output:
Display using for-loop: [zero] [three] [five] [seven] [nine] Display using iterator: zero three five seven nine
The following is the same code as in Listing.15, except that we are using method references:
Notice the use of the newly added default method forEach(Consumer<T>) in Java 8 to the List collections interface.
The syntax of method reference to a static method of a class is - class-name::method-name and that of an instance method of a class is - object-reference::method-name.