Java 8 Lambda expressions tutorial

What are Lambda expressions

Lambda expressions are a new feature of Java 8 that can be seen as replacement of anonymous inner classes using expressions, allowing to pass functionality as method arguments. They can also be seen as anonymous methods (nameless) implementing a method defined by a functional interface.
Before going further, I will describe what a functional interface is in the next section.

Functional Interfaces

A functional interface (FI) is any interface that contains :

  1. One single abstract method;
  2. Zero or more static methods;
  3. Zero or more default methods.

To be able to write a Lambda expression you must have a corresponding functional interface. Lambda expressions implement the method defined by that interface. The signature of it’s abstract method must match the lambda expression in terms of parameters and return type.

It is preferable to add the annotation @FunctionalInterface to your FI but it’s not mandatory. However, doing so will make your code more readable and allows the EDI (ie: eclipse) to give you some refactoring suggestions like replacing anonymous inner classes by lambda expressions. Functional interfaces are also called Single Abstract Methods (SAM).

Th package java.util.function includes a lot functional interfaces that can be used in specific situation. An example with Predicate and Consumer functional interfaces will be discussed later in this tutorial. You can check the Javadoc for the complete list of these functional interfaces. Before you define your own FIs, you may want to verify the existence of an FI that satisfies your specific need.

Why lambda expressions

The following simple example will help you to better understand the use of lambda expressions.

Consider the Operation interface, representing an abstract arithmetic operation. The interface contains the calculate() method representing the calculating functionality. Since the interface offers only and only one abstract method, we can consider that the interface Operation itself represents the calculate functionality:

interface Operation {
  int calculate(int a,int b);
}

Please note that the Operation interface contains one and only one abstract method which is calculate(), such interfaces are called Functional Interfaces.

The Test class contains the displayResult() method that calls the method calculate of Operation and displays the result. It takes three parameters:

  1. The first parameter of the calculation;
  2. The second parameter of the calculation;
  3. The Operation we want to execute.
public class Test {

 public static void main(String[] args) {
   // calculations to be added later ...
 }

 static void displayResult(Operation operation, int a, int b){
   System.out.println(operation.calculate(a, b));
 }

}

In other words, the displayMethod() will receive the calculation functionality as parameter, execute it and display the result.

In Java 7 and earlier, we can implement a calculation by implementing the interface Operation. In the following example, I will define an addition and multiplication operations :

// Addition
public class PlusOperation implements Operation {

 @Override
 public int calculate(int a, int b) {
  return a+b;
 }

}

// Multiplication
public class MultiplicationOperation implements Operation {

 @Override
 public int calculate(int a, int b) {
  return a*b;
 }

}

We can now instantiate the concrete operations (Addition and Multiplication) inside the Test class, and pass them as argument to the Test.displayResult() method:

// Addition of 1 and 2
Operation plusOperation = new PlusOperation();
displayResult(1, 2, plusOperation);

// Multiplication of 4 by 4
Operation multiplicationOperation = new MultiplicationOperation();
displayResult(4, 4, multiplicationOperation);

An other option to implement a custom calculation is to create an anonymous inner class in place, and pass it directly to the displayResult() method as parameter. Let’s say we want to implement the exponent of 10 in 20 :

// other operation, 10 exponent 20
displayResult( 10, 20,
 new Operation() {
  @Override
  public int calculate(int a, int b) {
   return a^b;
  }
 }
);

 

All the examples above can be implemented in less lines of code using Lambda expressions. We will see in the next section how to write a lambda expression, but remember that a lambda expression represents a behavior, a functionality we can pass as a method argument:

// the above operations using lambda expressions
displayResult(1,2, (a,b)->a+b); // addition
displayResult(4,4, (a,b)->a*b); // multiplication
displayResult(10,20, (a,b)-> a^b); // exponent

The final code of the Test class looks like this :

public class Test {

 public static void main(String[] args) {

  // Addition of 1 and 2
  Operation plusOperation = new PlusOperation();
  displayResult(1, 2, plusOperation);

  // Multiplication of 4 by 4
  Operation multiplicationOperation = new MultiplicationOperation();
  displayResult(4, 4, multiplicationOperation);

  // other operatio, 10 exponent 20
  displayResult( 10, 20,
  new Operation() {

   @Override
   public int calculate(int a, int b) {
    return a^b;
   }
  }
  );

  // all the above operations using lambda expressions

  displayResult(1,2, (a,b)->a+b); // addition
  displayResult(4,4, (a,b)->a*b); // multiplication
  displayResult(10,20, (a,b)-> { // exponent
   return a^b;
   }
  );

 }

 static void displayResult( int a, int b, Operation operation){
  System.out.println(operation.calculate(a, b));
 }

}

 

And the output of the program is the same using lambda expressions or the old way:

3 ⇐ Using the old way
16
30
3 ⇐ Using lambda expressions
16
30

How to write a Lambda Expression

Lambda expressions are composed of two statements, linked by an arrow (->):

  1. The left statement: Represents the parameters statement;
  2. The right statement : Represents the body statement .
// general syntax
(parameters) -> (Body)

Lambda’s left statement

The left statement of a lambda expression is similar to the parameters statement in a method definition, with the difference that you can choose to indicate or not the data types of the parameters. If there are no parameters, you can simply use empty parentheses.
The parentheses become optional if the left statement contains one single parameter.

Lambda’s right statement

The right statement of a lambda expression is similar to the body of a method definition, with the difference that the curly braces ( { } ) and the return keyword are optional if the body contains one single expression. However, this becomes necessary when the body statement is composed of more than one expression;

 

Examples of valid lambda expressions

The following is a list of valid lambda expressions:

// Full syntax
(Double x, Double y) -> {return x * y;}

// Omitting curly braces
(Double x, Double y) -> return x * y

// Omitting curly braces and return keyword
(Double x, Double y) -> x * y

// Omitting the data types
(x, y) -> {return x * y;}

// The parentheses become optional if the left side
// contains only one parameter
x -> System.out.print(x)

// Lambda expression including many
// statements in the body
(x,y) -> {
 if(x>y){
  System.out.println("x is greater than y");
  return true;
 }else{
  System.out.println("y is greater or equal to x")
 }
 return false;
}

// Lambda expression with no parameters
() -> 99

 

Use case of lambda with the Collections framework

Streams have been introduced in Java 8 to support the Collections framework.
A Stream is a sequence of elements supporting sequential and parallel aggregate operations.
Some of these operations are simply methods that can take lambda expressions as parameter, allowing the programmer to process data in a simpler way compared to previous versions of Java.

To obtain a stream from a collection you can use the stream() method to obtain a sequential stream, and parallelStream() to obtain a parallel stream. For more informations about streams check this page talking about streams in java 8.

In the following example I will show how to filter and display a List in two lines of code, using lambda expressions and streams. Here are the steps:

  1. Create a list of Integers ;
  2. Populate it with random integers;
  3. Filter the list and keep only the pair numbers;
  4. Display the list.

To Create the list, I will simply use an ArrayList of Integers :

List<Integer> list = new ArrayList<Integer>();

The populateList() method populates the list with 1000 random integers:

// Implementation of the populateList() method
public static void populateList(List<Integer> list){
 Random random = new Random();
 for(int i=0;i<1000;i++){
  list.add(random.nextInt());
 }
}


// calling the populateList() method
populateList(list);

I will then obtain a stream from the list using the stream() method, filter it using the filter() method and collect the results into a filtered new list.

List<Integer> filteredList = list.stream().filter(e -> e%2==0).collect(Collectors.toList());

I will explain every element in the above line of code:

  • The stream() method returns a sequential stream obtained from the list we created before;
  • The filter() method is obtained from the stream, as you can understand from it’s name, the filter() method allow to filter the stream using a lambda expression;
  • The filter() method takes an object of type Predicat as parameter, it’s a functional Interface, that means we can use a lambda expression;
  • The Predicate functional interface is included in the package java.util.function (as well as other useful FIs). According to the signature of it’s abstract method ( boolean test(T t) ) It represents a boolean-valued function of one argument.
  • Finally, we must convert the stream to a List again, we will use the collect() method with the parameter (Collectors.toList()) .

Please note that the chaining the methods above is called a Pipeline, and all the operations of that pipeline are executed only after the method collect() is called.

Once we got a filteredList, we can display it. The method forEach( ) is a new facility added to the Collections framework allowing to iterate through the elements of a list. It takes an object of type Consumer (an other functional interface of the package java.util.function).

The consumer functional interface represents an operation that takes a single input argument and returns no value, this is similar to the behavior we want : displaying a value. Here is how to display the filteredList in Java 8 :

filteredList.forEach(e -> System.out.println(e));

To recapitulate, our code will look like this :

// Create the list
List<Integer> list = new ArrayList<Integer>();
    
// Populate the list
populateList(list);

// Filter the list
List<Integer> filteredList = list.stream().filter(e -> e%2==0).collect(Collectors.toList());

// Display the elements of the list
filteredList.forEach(e -> System.out.println(e));

Conclusion

In this tutorial we saw what are lambda expressions, why and how to use them. Using Lambda expressions allows to write less code and helps manipulation data withing the Collections framework, streams and pipelines  (iteration, filtering, sorting, etc) beside other things.  However, lambda expressions come with some limitations, for example, if there are problems in your code debugging may be harder with lambda expressions.