This article of the “Java SE 8 new features tour” series will deep dive into understanding Lambda expressions. I will show you a few different uses of Lambda Expressions. They all have in common the implementation of functional interfaces. I will explain how the compiler is inferring information from code, such as specific types of variables and what is really happening in the background.
In the previous article “Java SE 8 new features tour: The Big change, in Java Development world”, where I have talked about what we are going to explore during this series. I have started by an introduction to Java SE 8 main features, followed by installation process of JDK8 on both Microsoft windows and Apple Mac OS X platforms, with important advices and notice to take care of.
Finally, we went through a development of a console application powered by Lambda expression to make sure that we have installed Java SE 8 probably.
Source code is hosted on my Github account: Clone from HERE.
What is Lambda expression?
Perhaps the best-known new feature of Java SE 8 is called Project Lambda, an effort to bring Java into the world of functional programming.
In computer science terminology;
A Lambda is an anonymous function. That is, a function without a name.
In Java;
All functions are members of classes, and are referred to as methods. To create a method, you need to define the class of which it's a member.
A lambda expression in Java SE 8 lets you define a class and a single method with very concise syntax implementing an interface that has a single abstract method.
Let's figure out the idea.
Lambda Expressions lets developers simplify and shorten their code. Making it more readable and maintainable. This leads to remove more verbose class declarations.
Let's take a look at a few code snippets.
- Implementing an interface:
Prior to Java SE 8, if you wanted to create a thread, you'd first define a class that implements the runnable interface. This is an interface that has a single abstract method named Run that accepts no arguments. You might define the class in its own code file. A file named by MyRunnable.java. And you might name the class, MyRunnable, as I've done here. And then you'd implement the single abstract method.
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("I am running");
}
public static void main(String[] args) {
MyRunnable r1 = new MyRunnable();
new Thread(r1).start();
}
}
In this example, my implementation outputs a literal string to the console. You would then take that object, and pass it to an instance of the thread class. I'm instantiating my runnable as an object named r1. Passing it to the thread's constructor and calling the thread's start method. My code will now run in its own thread and its own memory space.
- Implementing an inner class:
You could improve on this code a bit, instead of declaring your class in a separate file, you might declare it as single use class, known as an inner class, local to the method in which it's used.
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("I am running");
}
};
new Thread(r1).start();
}
So now, I'm once again creating an object named r1, but I'm calling the interface's constructor method directly. And once again, implementing it's single abstract method. Then I'm passing the object to the thread's constructor.
- Implementing an anonymous class:
And you can make it even more concise, by declaring the class as an anonymous class, so named because it's never given a name. I'm instantiating the runnable interface and immediately passing it to the thread constructor. I'm still implementing the run method and I'm still calling the thread's start method.
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I am running");
}
}).start();
}
- Using lambda expression:
In Java SE 8 you can re-factor this code to significantly reduce it and make it a lot more readable. The lambda version might look like this.
public static void main(String[] args) {
Runnable r1 = () -> System.out.println("I am running");
new Thread(r1).start();
}
I'm declaring an object with a type of runnable but now I'm using a single line of code to declare the single abstract method implementation and then once again I'm passing the object to the Thread's constructor. You are still implementing the runnable interface and calling it's run method but you're doing it with a lot less code. In addition, it could be improved as the following:
public static void main(String[] args) {
new Thread(() -> System.out.println("I am running")).start();
}
Here is an important quote from an early specs document about Project Lambda.
Lambda expressions can only appear in places where they will be assigned to a variable whose type is a functional interface.
Quote By Brian Goetz
Let's break this down to understand what's happening.
What are the functional interfaces?
A functional interface is an interface that has only a single custom abstract method. That is, one that is not inherited from the object class. Java has many of these interfaces such as Runnable, Comparable, Callable, TimerTask and many others.
Prior to Java 8, they were known as Single Abstract Method or SAM interfaces. In Java 8 we now call them functional interfaces.
Lambda Expression syntax
This lambda expression is returning an implementation of the runnable interface; it has two parts separated by a new bit of syntax called the arrow token or the Lambda operator. The first part of the lambda expression, before the arrow token, is the signature of the method you're implementing.
In this example, it's a no arguments method so it's represented just by parentheses. But if I'm implementing a method that accepts arguments, I would simply give the arguments names. I don't have to declare their types.
Because the interface has only a single abstract method, the data types are already known. And one of the goals of a lambda expression is to eliminate unnecessary syntax. The second part of the expression, after the arrow token, is the implementation of the single method's body.
If it's just a single line of code, as with this example, you don't need anything else. To implement a method body with multiple statements, wrap them in braces.
Runnable r = ( ) -> {
System.out.println("Hello!");
System.out.println("Lambda!");
};
Lambda Goals
Lambda Expressions can reduce the amount of code you need to write and the number of custom classes you have to create and maintain.
If you're implementing an interface for one-time use, it doesn't always make sense to create yet another code file or yet another named class. A Lambda Expression can define an anonymous implementation for one time use and significantly streamline your code.
Defining and instantiating a functional interface
To get started learning about Lambda expressions, I'll create a brand new functional interface. An interface with a single abstract method, and then I'll implement that interface with the Lambda expression.
You can use my source code project "JavaSE8-Features" hosted on github to navigate the project code.
- Method without any argument, Lambda implementation
In my source code, I'll actually put the interface into its own sub-package ending with lambda.interfaces. And I'll name the interface, HelloInterface
.
In order to implement an interface with a lambda expression, it must have a single abstract method. I will declare a public method that returns void, and I'll name it doGreeting
. It won't accept any arguments.
That is all you need to do to make an interface that's usable with Lambda expressions. If you want, you can use a new annotation, that's added to Java SE 8, named Functional Interface.
@FunctionalInterface
public interface HelloInterface {
void doGreeting();
}
Now I am ready to create a new class UseHelloInterface
under lambda.impl package, which will instantiate my functional interface (HelloInterface
) as the following:
public class UseHelloInterface {
public static void main(String[] args) {
HelloInterface hello = ()-> out.println("Hello from Lambda expression");
hello.doGreeting();
}
}
Run the file and check the result, it should run and output the following.
------------------------------------------------------------------------------------
--- exec-maven-plugin:1.2.1:exec (default-cli) @ Java8Features ---
Hello from Lambda expression
------------------------------------------------------------------------------------
So that's what the code can look like when you're working with a single abstract method that doesn't accept any arguments. Let's take a look at what it looks like with arguments.
- Method with any argument, Lambda implementation
Under lambda.interfaces. I'll create a new interface and name it CalculatorInterface
. Then I will declare a public method that returns void, and I will name it doCalculate
, which will receive two integer arguments value1
and value2
.
@FunctionalInterface
public interface CalculatorInterface {
public void doCalculate(int value1, int value2);
}
Now I am ready to create a new class Use CalculatorInterface
under lambda.impl package, which will instantiate my functional interface (CalculatorInterface
) as the following:
public static void main(String[] args) {
CalculatorInterface calc = (v1, v2) -> {
int result = v1 * v2;
out.println("The calculation result is: "+ result);
};
calc.doCalculate(10, 5);
}
Note the doCalculate()
arguments, they were named value1
and value2
in the interface, but you can name them anything here. I'll name them v1 and v2. I don't need to put in int before the argument names; that information is already known, because the compiler can infer this information from the functional interface method signature.
Run the file and check the result, it should run and output the following.
------------------------------------------------------------------------------------
--- exec-maven-plugin:1.2.1:exec (default-cli) @ Java8Features ---
The calculation result is: 50
------------------------------------------------------------------------------------
BUILD SUCCESS
Always bear in mind the following rule:
Again, you have to follow that rule that the interface can only have one abstract method. Then that interface and its single abstract method can be implemented with a lambda expression.
- Using built-in functional interfaces with lambdas
I've previously described how to use a lambda expression to implement an interface that you've created yourself.
Now, I'll show lambda expressions with built in interfaces. Interfaces that are a part of the Java runtime. I'll use two examples. I'm working in a package called lambda.builtin, that's a part of the exercise files. And I'll start with this class. UseThreading
. In this class, I'm implementing the Runnable interface. This interface's a part of the multithreaded architecture of Java.
My focus here is on how you code, not in how it operates. I'm going to show how to use lambda expressions to replace these inner classes. I'll comment out the code that's declaring the two objects. Then I'll re-declare them and do the implementation with lambdas. So let's start.
public static void main(String[] args) {
Runnable thrd1 = () -> out.println("Hello Thread 1.");
new Thread(thrd1).start();
new Thread(() -> out.println("Hello Thread 2.")).start();
}
Let's look at another example. I will use a Comparator. The Comparator is another functional interface in Java, which has a single abstract method. This method is the compare
method.
Open the file UseComparator
class, and check the commented bit of code, which is the actual code before refactoring it to lambda expression.
public static void main(String[] args) {
List<string> values = new ArrayList();
values.add("AAA");
values.add("bbb");
values.add("CCC");
values.add("ddd");
values.add("EEE");
sort(values);
out.println("Simple sort:");
print(values);
sort(values,(o1, o2) -> o1.compareToIgnoreCase(o2));
out.println("Sort with Comparator");
print(values);
}</string>
As before, it doesn't provide you any performance benefit. The underlying functionality is exactly the same. Whether you declare your own classes, use inner or anonymous inner classes, or lambda expressions, is completely up to you.
In the next article of this series, we will explore and code how to traverse the collections using lambda expression, filtering collections with Predicate interfaces, Traversing collections with method references, implementing default
methods in interfaces, and finally implementing static methods in interfaces.
Resources
- The Java Tutorials, Lambda Expressions
- JSR 310: Date and Time API
- JSR 337: Java SE 8 Release Contents
- OpenJDK website
- Java Platform, Standard Edition 8, API Specification