In this article, you will learn about what exactly lambda expressions and how to use it in java.

Prerequisites:

  1. Knowledge in java

Introduction:

Lambda expressions are introduced in java8 version. While java introducing this feature, It’s new for java. Not for other languages.

Before lambda expressions:

Before jumping into the lambda expression, we need to know what’s the problem java developers faced before introducing the lambda expressions.

Example 1:

Let’s take an example of creating thread before lambda expressions are introduced.

class MyRunnable implements Runnable {
 public void run() {
 System.out.println("I am executed");
 }
}
public class BeforeLambda {
 public static void main(String args[]) {
 Runnable runnableObj = new MyRunnable();
 Thread t = new Thread(runnableObj);
 t.start();
 }
}

The internal implementation of Runnable is

package java.lang;
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Don’t worry about @FunctionalInterface for now, we will see what is the use of FunctionalInterface in this article.

A deep dive into the Java lambda expressions - Image 1

Now compiling this code, the Java compiler will generate below two class files.

Now let’s reduce this code using an anonymous inner class.

Example 2:

public class BeforeLambda2 {
 public static void main(String args[]) {
 Runnable runnableObj = new Runnable() {
 public void run() {
 System.out.println("I am executed");
 }
 };
 Thread t = new Thread(runnableObj);
 t.start();
 }
}

Now Let’s compile this code and see how Java compiler-generated class files,

A deep dive into the Java lambda expressions - Image 2

Here, the Java compiler creates two class files for the main class and another for the anonymous inner class.

Problems before lambda expressions:

let’s analyze our anonymous inner class, Does this code has any redundant information?

Runnable runnableObj = new Runnable() {
 public void run() {
 System.out.println("I am executed");
 }
};

Here we are creating a reference for Runnable interface, so redundant information new Runnable() is not necessary here.

Runnable runnableObj =̵ n̶̵̶e̶̵̶w̶̵̶ ̶R̶̵̶u̶̵̶n̶̵̶n̶̵̶a̶̵̶b̶̵̶l̶̵̶e̶̵̶(̶̵̶)̶̵̶ ̶̵̶{̶̵̶
      public void run() {
		System.out.println("I am executed");
	}
}̶̵̶;̶̵̶

Here We are creating a reference for Runnable, and Runnable is an interface it only contains one method named run(). so we have the only way to provide the implementation for run() method. so redundant information public void run() is not necessary for an anonymous inner class.

Runnable runnableObj = n̶̵̶e̶̵̶w̶̵̶ ̶R̶̵̶u̶̵̶n̶̵̶n̶̵̶a̶̵̶b̶̵̶l̶̵̶e̶̵̶(̶̵̶)̶̵̶ ̶̵̶{̶̵̶
	p̶u̶b̶l̶i̶c̶ ̶v̶o̶i̶d̶ ̶r̶u̶n̶(̶)̶ ̶{̶
		System.out.println("I am executed");
	}̵
}̶̵̶;̶̵̶

Let’s see if the interface has some parameters with the return type.

interface AddInterface {
 public int add(int a, int  b);
}

Anonymous inner class will be,

AddInterface testObj = new AddInterface() {
 public int add(int a, int b) {
 return a + b;
 }
}

testObj is the reference for TestInterface, Types of parameters in the anonymous inner class are already available in the interface as well. so it’s redundant.

AddInterface testObj = n̶e̶w̶ ̶A̶d̶d̶I̶n̶t̶e̶r̶f̶a̶c̶e̶(̶)̶ ̶{̶
      p̶u̶b̶l̶i̶c̶ ̶i̶n̶t̶ ̶a̶d̶d̶(inta,   int   b  ) {
		return a + b;
	}
}̶̵̶;̶̵̶

Also, class files are getting generating for an anonymous inner class.

So Java developers consider these problems and introduced lambda expressions.

Lambda expressions:

Syntax:

parameters -> expressions

Example 1:

Before lambda:

Runnable runnableObj = n̶̵̶e̶̵̶w̶̵̶ ̶R̶̵̶u̶̵̶n̶̵̶n̶̵̶a̶̵̶b̶̵̶l̶̵̶e̶̵̶(̶̵̶)̶̵̶ ̶̵̶{̶̵̶
	p̶u̶b̶l̶i̶c̶ ̶v̶o̶i̶d̶ ̶r̶u̶n̶(̶)̶ ̶{̶
		System.out.println("I am executed");
	}̵
}̶̵̶

After Lambda:

Runnable runnableObj = () -> {
 System.out.println("I am executed");
};

If lambda expression contains single-line expressions, we even don’t need curly braces.

so finally,

Runnable runnableObj = () -> System.out.println("I am executed");

Final code will be,

public class AfterLambda {
 public static void main(String args[]) {
 Runnable runnableObj = () -> System.out.println("I am executed");
 Thread t = new Thread(runnableObj);
 t.start();
 }
}

We can pass lambda expression directly into the Thread class parameter as well like below,

public class AfterLambda {
 public static void main(String args[]) {
 Thread t = new Thread(() -> System.out.println("I am executed"));
 t.start();
 }
}

Example 2 (With Parameters):

Before Lambda:

interface AddInterface {
 public int add(int a, int  b);
}

Anonymous inner class will be,

AddInterface testObj = n̶e̶w̶ ̶A̶d̶d̶I̶n̶t̶e̶r̶f̶a̶c̶e̶(̶)̶ ̶{̶
      p̶u̶b̶l̶i̶c̶ ̶i̶n̶t̶ ̶a̶d̶d̶ (inta,   int   b  ) {
		return a + b;
	}
}̶̵̶;̶̵̶

After Lambda:

AddInterface testObj = (int a, int b)-> return a + b;

Here, type of the parameters are already known in the interface declaration, so we can remove types of the parameters as well,

AddInterface testObj = (a, b) -> return a + b;

even we don’t need a return keyword if its a single statement.

AddInterface testObj = (a, b) -> a + b;

Finally, code will be,

public class AfterLambda {
 public static void main(String args[]) {
 AddInterface testObj = (a, b) -> a + b;
 System.out.println(testObj.add(10, 20)); //30
 }
}

An important rule of lambda expression and Functional interface:

Lambda expressions can be created only for the interface which contains only one method declaration,

In this article we also created lambda expressions which accept this rule, we cannot use a lambda expression if the interface has two or more method declarations.

we know the Runnable interface, which only contains one run() method declaration.

Another one of our own interface AddInterface, which also contains only one unimplemented method named add() method.

In order to mention the interface which should contain only one interface, we mark those interface with @FunctionalInterface annotation. We have already seen this annotation in the internal implementation of the Runnable interface.

On adding @FunctionalInterface above the interface, those interfaces must have only one unimplemented method. If we break this rule, the java compiler will throw compile-time error as below.

Invalid ‘@FunctionalInterface’ annotation; AddInterface is not a functional interface

In Java8 introduces streams which use functional interface, Let’s see those in detail in another article.

Happy Programming !!!