A major feature of the Java 8 release is the language-level support for lambda expressions, which brings Java into the realm of functional-style programming. Other object-oriented programming languages such as Scala, JavaScript and Python had lambda expressions built in from the start, and now Java has joined the rank.
Why is this interesting? Functional programming (FP) is based on the recursive evaluation of pure functions, whose output depends solely on the input arguments to the function, avoiding side-effects (i.e. changing the state of objects). This immutability results in code that behaves more predictably and performs better in concurrent and parallel systems. This is the first of a series of posts that will explore functional programming in Java, and the starting point is to understand lambda expressions (also known as closures or first-class functions).
So, what is a lambda expression? Put simply, it is a concise syntax to create a reference to a method implementation for later execution. In Java, lambda expressions are used in conjunction with functional interfaces. A functional interface is a plain old interface that defines just one single abstract method, and is marked with the @FunctionalInterface annotation.
It works like this: you define a variable of a functional interface type, and assign a lambda expression to it. This variable can then be passed around just like any other reference, but instead of containing a reference to an object, it contains a reference to a method implementation.
Confusing? Do not worry; it will all become clear with a few practical examples. By the way, to follow the examples in this post, you need a development environment that supports Java 8. If you don’t have one, you can follow this tutorial to get it setup.
For our first example, we will use the Runnable functional interface, whose API documentation can be found here:
https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html
The Runnable interface has been around for a long time, and if you wrote multi-threaded programs in Java you will be familiar with it; it is used to define tasks for later execution in a separate thread.
If you look at the source code of Runnable, you will see that it is annotated with @FunctionalInterface. It is not mandatory to use the annotation, in fact any interface that declares a single abstract method can be used as a functional interface; but it is good practice to annotate it so the compiler will check that it is a valid definition.
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
By the way, if you don’t know where to find the source code of the Java library classes, this post will show you:
http://robertovormittag.net/learn-from-the-experts-a-look-at-the-java-source-code/
Enough talk. Let’s write some code. Create a Java console application on your IDE, and place this code in main()
public static void main(String[] args) {
Runnable r = () -> {
System.out.println("Hello lambda!");
};
Thread t1 = new Thread(r);
t1.start();
}
Code analysis
First, we have declared a variable r of type Runnable, and assigned a lambda expression to it. This automagically becomes the implementation of the run() method declared by the interface.
Next, we have built a Thread object passing to the constructor our Runnable implementation, and started the thread. If you run the app, the thread will run and the string “Hello from a lambda expression!” will be printed on the console.
Syntax analysis
Let’s now take a closer look at the syntax of the lambda expression. All lambda expressions consist essentially of three parts:
- a set of parameters (in parenthesis)
- an arrow (or rocket) operator
- a body (with the method implementation)
In this case, because the run() method takes no parameters, we have an empty parenthesis at the start of the expression.
Let’s look at another example, this time with parameters, and implement the java.util.Comparator functional interface, defined here:
https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html
This interface declares the abstract method
int compare(T o1, T o2)
which compares its two arguments for order, and returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
Note from the API documentation that Comparator also declares other non-abstract, static and default methods but that is OK; it still is a valid functional interface, as it declares only a single abstract method.
We will now implement this interface so that it compares two String objects based on length. Here is the code:
public static void main(String[] args) {
Comparator<String> comp = (String str1, String str2) -> {
return Integer.compare( str1.length(), str2.length());
};
int result = comp.compare("ab", "abc");
System.out.println(result);
}
Here we declare the variable comp of type Comparator<String> and assign it to the lambda expression, starting with the two String parameters in parenthesis, followed by the rocket operator, followed by the function body which returns a value. We use the Integer class utility method compare() to compare the length of the String arguments.
https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#compare-int-int-
The variable comp becomes a reference to our method implementation, and can be used in a way similar to the way object references are used. In the next line, we use comp to call our implementation passing two arguments “ab” and “abc”, capturing the return value in the variable result, which is printed to stdout.
If you run the application, you will see the value -1 printed, as expected, since the length of “ab” is less than that of “abc”.
An even more concise syntax
If the compiler can work out the type of the method parameters (in this case String), it is not even necessary to specify them (this feature is called type inference). And, if the method body is limited to a single expression, you can omit the angle brackets {} and the return keyword.
So our implementation can be written even more concisely like this:
public static void main(String[] args) {
Comparator<String> comp = (str1, str2) ->
Integer.compare( str1.length(), str2.length());
int result = comp.compare("ab", "abc");
System.out.println(result);
}
If you run the new version of the app, you will get the same result. How much concise you want to be is simply a matter of personal preference and programming style. Personally speaking, I value more much more readability and clarity in the code than conciseness, but the choice is there.
We can make a more interesting use of our Comparator implementation by applying it to the List sorting algorithm in the Collections utilities:
https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#sort-java.util.List-java.util.Comparator-
This utility sorts the specified list according to the order induced by the specified comparator. Try the following code:
public static void main(String[] args) {
Comparator<String> comp = (String str1, String str2) -> {
return Integer.compare( str1.length(), str2.length());
};
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("abcdef");
list.add("ab");
System.out.println(list);
Collections.sort(list, comp);
System.out.println(list);
}
Here, we pass comp (the reference to our implementation of Comparator<String>) as the second parameter to the Collections.sort method.
If you run the app, you can check from the console output that, after the call to Collections.sort, the list has been sorted according to our Comparator implementation.
We covered a lot of ground, it is time to summarize. This is what we have learned so far:
- What is a lambda expression
- What is a functional interface
- How lambda expressions and functional interfaces work together
- The syntax of lambda expressions
- A couple of practical examples using threads and collection algorithms
In the next post we will look at method references. Feel free to leave any comments / suggestions. Thank you for reading.