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:
- Create a class that implements the interface
- Override the method in the interface and provide an implementation
- 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.