Lambda expressions in Java 8

Lambda expressions are a new feature introduced in Java 8, they're Java’s first step towards functional programming. Java is an object-oriented language, however, object-oriented code sometimes tends to be lengthy and verbose. Java 8's lambda expressions enable programmers to overcome this issue by writing code as functions. This gets rid of a lot of boilerplate code and helps to keep things clean.

What are Lambda expressions?

Lambda expressions specify anonymous functions, that is a function without a name. So the lambda expression just specifies the method parameters and method body. Since Java is inherently object-oriented, lambda expressions cannot be used on their own, they need to be associated with an object. Java 8 introduced functional interfaces to support lambda expressions. So lambda expressions and functional interfaces together help in writing clean and concise code. Lambda expressions also allow passing code around as a method parameter or assigning it to a variable. Before I dive into lambda expressions, it is necessary to have some understanding of functional interfaces, so let's cover them briefly. 

Functional Interfaces

Functional interfaces are also a new feature introduced in Java 8. A functional interface is basically an interface that has a single abstract method. Java already had interfaces with single methods like Runnable and Comparator. Starting with Java 8, these were officially designated as functional interfaces.

In addition, users can also create their own functional interfaces. A functional interface has the annotation@FunctionalInterface although it is completely optional. Any interface with a single abstract method is treated as a functional interface. 

Consider the following code snippet:

@FunctionalInterface

public interface MyFunctionalInterface {

public void method1();

}

Here, the MyFunctionalInterface is a functional interface with a single method called method1

Traditional Approach

Prior to Java 8, we had to write code similar to the following in order to implement the interface defined above:

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {

@Override

public void method1() {

System.out.println("In method1");

}

public static void main(String[] args) {

MyFunctionalInterfaceImpl myFunctionalInterfaceObj = new MyFunctionalInterfaceImpl();

myFunctionalInterfaceObj.method1();

}

}

So, this code consists of the following steps:

  1. Create a class that implements the interface
  2. Override the method in the interface and provide an implementation 
  3. Write code that invokes the interface method – in this case, the main method

This seems like a lot of code to be written in order to implement a single method. Another option is to create an Anonymous class that implements the functional interface as follows:

public class MyFunctionalInterfaceImpl2 {

public static void main(String[] args) {

MyFunctionalInterface myFunctionalInterfaceObj = new MyFunctionalInterface() {

@Override

public void method1() {

System.out.println("In method1");

}

};

myFunctionalInterfaceObj.method1();

}

}

Although this code is slightly better than the code above, it is still bulky and hard to understand.

The Java 8 Way

We can rewrite the code above using lambda expressions as follows:

public class Main {

public static void main(String args[]){

MyFunctionalInterface myInterface = () -> System.out.println("In method1");

myInterface.method1();

}

}

So here, a lambda expression is used to implement the method in the functional interface. This eliminates the need to create a class that implements the interface. So this code is much cleaner as compared to the code written via the traditional approach. 

Lambda expression syntax

The general syntax of the lambda expression is as follows:

(parameters) -> {body}

So the lambda expression consists of the parameters to the expression, the lambda operator and the expression body.

In the above example we have used a lambda expression as follows:

() -> System.out.println("In method1");

Let's break down this lambda expression to understand it better.

Definition

So just to define a lambda expression again in the context of functional interfaces, a lambda expression provides an implementation for the method in the functional interface in a concise manner, without having to create a class that implements the interface.

No function name

As mentioned above, a lambda expression specifies an anonymous function or a function with no name. As you can see, a method name is not specified here.

Parameters

The first part of the lambda expression are the parameters specified in parentheses. Here we are just specifying empty parentheses i.e. (). This means that the lambda expression does not accept any parameters. Since the method method1 in the MyFunctionalInterface does not accept any arguments, no parameters are passed in.

Lambda Operator

The parameters to the lambda expression are followed by the lambda operator i.e. ->

Expression Body

The lambda operator is followed by the expression body. In this case, the expression body consists of just one line of code that is the Sysout statement.

Functional Interface Assignment

Here, the lambda expression is assigned to the functional interface as follows:

MyFunctionalInterface myInterface = () -> System.out.println("In method1");

As mentioned earlier, the lambda expression must be associated with a functional interface. It should be assigned to the functional interface whose method it implements. Since here it implements MyFunctionalInterface it is assigned it to myInterface which is of the type.MyFunctionalInterface

Lambda Expression Syntax Pointers

A few points to remember regarding the syntax of the lambda expression:

  • A lambda expression can receive any number of parameters
  • The parameters are specified in parentheses and separated by commas
  • The parentheses need not be specified if the lambda expression receives a single parameter
  • Empty parentheses mean that no parameters are passed in
  • Type of the parameter is optional if it is not specified, it is inferred
  • The body of the lambda expression can contain any number of statements
  • The curly brackets around the body need to be specified only when there is more than one statement in the body, otherwise, they need not be specified


Examples of Lambda Expressions

Let’s take a look at different examples of lambda expressions:

1. Single parameter with no return type

Consider the following Functional Interface:

public interface Calculator {

public void add(int number);

}

This interface has a method called add. It accepts a single integer parameter. It does not return anything.

public class Main {

public static void main(String args[]){

Calculator calc = (num) -> System.out.println("Input is "+num);

calc.add(5);

}

}

So here, a lambda expression is specified for the add method in the Calculator interface. A single integer parameter is passed into the expression. The expression body simply prints the input number.

When this code is executed, it will print the following output:

Input is 5

2. Two parameters and return type

public interface Calculator {

public int add(int number1,int number2);

}

This interface has a method called add that accepts 2 integers and returns an integer value.

public class Main {

public static void main(String args[]){

Calculator calc = (num1,num2) -> {return num1+num2;};

int result = calc.add(5,7);

System.out.println("Result is "+result);

}

}

Again, a lambda expression is specified for the add method. Two integer values i.e. num1 and num2 are passed into the lambda expression which returns their sum.

When this code is executed, it will print the following output:

Result is 12

Benefits of lambda expressions

Concise code

As seen in the above examples, lambda expressions along with functional interfaces help write concise code and eliminate the need for all the boilerplate code required via the traditional approach.

 Provide different implementations

Lambda expressions offer the ability to provide different implementations for the same method on the fly. Since lambda expressions allow supplying an inline implementation for a method in a functional interface, different implementations can be provided for the same functional interface. So consider the following interface:

@FunctionalInterface

public interface Animal {

public void speak();

}

And consider the following code that provides different implementations for the speak method via lambda expressions:

public class AnimalDemo {

public static void main(String[] args) {

Animal cat = () -> System.out.println("Meow");

Animal dog = () -> System.out.println("Bark");

Animal cow = () -> System.out.println("Moo");

cat.speak();

dog.speak();

cow.speak();

}

}

When this code is executed, it will print the following output:

Meow

Bark

Moo

So 3 different implementations are provided for the speak method in the interface Animal, each implementation prints a different output to the console. 

Passing around code

As mentioned above, lambda expressions allow passing around code as method arguments.

Consider the following code snippet: 

public class AnimalDemo2 {

public static void main(String[] args) {

saySomething(() -> System.out.println("Meow"));

saySomething(() -> System.out.println("Bark"));

saySomething(() -> System.out.println("Moo"));

}

public static void saySomething(Animal animal){

animal.speak();

}

}

Here, a class called AnimalDemo is defined. It has a method called saySomething that accepts an Animal instance. In the main method, instead of passing an Animal instance, a lambda expression is passed as a method parameter to the saySomething method.

Internal iteration

Prior to Java 8,  we had to write code similar to the following in order to iterate through a List:

public static void main(String[] args) {

List<Integer> list = Arrays.asList(2,4,8,10,12);

for(int num:list)

System.out.println(num);

}

So basically a for each loop needs to be written to iterate through the elements in the list.

Java 8 supports internal iteration, so an explicit for loop is not required.  So we can write code similar to the following:

 public static void main(String[] args) {

List<Integer> list = Arrays.asList(2,4,8,10,12);

list.forEach(num -> System.out.println(num));

}

So there is no explicit for loop, the forEach method is invoked on the List interface. This method iterates through all the elements in the list. For each element, it executes the code specified by the lambda expression, in this case, it simply prints the element.

The forEach method accepts a Consumer object as a parameter.  Consumer is a functional interface with a method that accepts a single parameter of any data type. So in the lambda expression, we are providing an implementation for this consumer interface. This implementation just prints the input number.

Conclusion

Lambda expressions provide a more concise way of programming for Java developers. Using lambda expressions, one can provide an inline implementation for a functional interface and do away with the need to write all the boilerplate code required to implement an interface via the traditional approach.