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.

 

Examples of lambda expressions used with forEach(), removeIf(), replaceAll() and sort() functions

In this tutorial I will cover the use of lambda expressions with the Collection’s functions forEach(), removeIf(), replaceAll() and sort() . The Lambda Expressions can replace a type of Functional Interface.

There are several Functional interfaces included in Java 8, located in the package java.util.function. The choice of any of these FIs depends on the situation. For example, in our tutorial, each of the mentioned functions takes a specific type type of Functional Interface (BiConsumer, Predicate, etc) .

forEach

Use case with java.util.Map

When used with a Map, the forEach() function takes in parameter an object of type BiConsumer, which is a specialization of the Consumer functional interface.

As a Map element is defined by a key and a value, the BiConsumer FI represents an operation that accepts two input arguments and returns no result.

So, for this example I will create a map that contains  the names of some of Real Madrid soccer team players as values, and their numbers as keys. Then, I will iterate through the players using the forEach function and display each one prefixed by his number.

Map<Integer, String> players = new HashMap<Integer, String>();
players.put(2, "Dani Carvajal");
players.put(19, "Achraf Hakimi");
players.put(4, "Sergio Ramos");
players.put(12, "Marcelo");
players.put(8, "Toni Kroos");
players.put(7, "Christano Ronaldo");

Now I will call the forEach() function from the players Map. I will create a functional interface of type BiConsumer to hold the lambda expression that will print each player.

BiConsumer<Integer, String> consumer = (k,v)-> System.out.println(k + " : "+ v);
players.forEach(consumer);

This can be simplified to only one line like the following :

players.forEach((k,v)-> System.out.println(k + " : "+ v));

The execution will print the following list:

2 : Dani Carvajal
19 : Achraf Hakimi
4 : Sergio Ramos
7 : Christano Ronaldo
8 : Toni Kroos
12 : Marcelo

 

Use case with java.util.List

For this example, I will use the same list of players but without the keys (the numbers) as I will be using a List instead of a Map.

When used with a List, the method forEach() takes an object of type Consumer as parameter.

List<String> players = new ArrayList<String>();
players.add("Dani Carvajal");
players.add("Achraf Hakimi");
players.add("Sergio Ramos");
players.add("Marcelo");
players.add("Toni Kroos");
players.add("Christano Ronaldo");

Then I will create my lambda expression of type Consumer that will take a parameter of type String and print it on the Console. The Consumer object ( or the lambda expression) will be passed as parameter to the forEach() function in order to display the player names.

Consumer<String> consumer = (player)-> System.out.println(player);
players.forEach(consumer);

This will print the following :

Dani Carvajal
Achraf Hakimi
Sergio Ramos
Marcelo
Toni Kroos
Christano Ronaldo

Of course, I could use only one line to simplify the code:

players.forEach((player)-> System.out.println(player));

 

removeIf

The remove if function takes an object of type java.util.function.Predicate as parameter.

A Predicate is a Functional Interface that represents a boolean-valued function of one argument. It means that the Lambda Expression I will create will take a parameter and return a boolean.

To show the use of the removeIf() function, I will create the same list but I will do an intentional mistake : I will add Messi the list of Real Madrid players, so I will use the removeIf function to delete the players with a name equal to Messi, then I will print the List.

// create the list
List<String> players = new ArrayList<String>();
players.add("Dani Carvajal");
players.add("Achraf Hakimi");
players.add("Sergio Ramos");
players.add("Marcelo");
players.add("Toni Kroos");
players.add("Christano Ronaldo");
players.add("Messi");

// create the lambda expression of type Predicate
Predicate<String> predicate = player -> player.equals("Messi");
players.removeIf(predicate);

// display the players
players.forEach((player)-> System.out.println(player));

The result of execution will be a list without Messi !

Dani Carvajal
Achraf Hakimi
Sergio Ramos
Marcelo
Toni Kroos
Christano Ronaldo

replaceAll

The replaceAll() function takes as parameter a Functional Interface of type UnaryOperator when used with a List. A UnaryOperator represents an operation on a single operand that produces a result of the same type as its operand.

In this example, and for a reason I don’t know, I will replace all the name of Christiano Ronaldo by ********* . Practicality, My Lambda Expression will take a parameter of type String  and return a value of the same time but after the replacement by ******** is done.

// create the list
List<String> players = new ArrayList<String>();
players.add("Dani Carvajal");
players.add("Achraf Hakimi");
players.add("Sergio Ramos");
players.add("Marcelo");
players.add("Toni Kroos");
players.add("Christano Ronaldo");

// create the lambda expression
UnaryOperator<String> unaryOperator = 
  s->{
   if(s.equals("Christano Ronaldo")){
    return "******";
   }else{
    return s;
   }
  };

// do the replacement 
players.replaceAll(unaryOperator);

// display the list
players.forEach((player)-> System.out.println(player));

The result will be as follow :

Dani Carvajal
Achraf Hakimi
Sergio Ramos
Marcelo
Toni Kroos
******

sort

The Collection’s sort() method takes two objects as parameter :

  1. The list being sorted;
  2. A Functional Interface of type Comparable (say an Interface of type Comparable for java 7 and bellow).

In previous versions of Java, we had to implement the Interface Comparable and override the compare() method in order to implement the way in which we want the comparison to be done.

In Java 8 however, All we have to do is to pass a Lambda Expression as second parameter, defining how the comparison is done.

in this example I will sort a list of players based on their score.

I will first create a Player class .

class Player {
   
   private String name;
   private Integer score;

   // Constructor with parameters
   public Player(String name, int score){
    this.name = name;
    this.score = score;
   }
   
   public String getName() {
    return name;
   }
   
   public void setName(String name) {
    this.name = name;
   }
   
   public Integer getScore() {
    return score;
   }
   
   public void setScore(Integer score) {
    this.score = score;
   }
   
   @Override
   public String toString() {
    return "[name : "+this.name+", score : "+this.score+"]";
   }
   
}

Then I will create a list of players with their scores, and passing  two parameters to the sort( ) function :

  1. The list to sort;
  2. a Lambda Expression determining how the comparison is done.
// Create a list of players
List<Player> playerList = new ArrayList<Player>();

// add some players, and their scores
playerList.add(new Player("Messi",500));
playerList.add(new Player("Ronaldo",750));
playerList.add(new Player("Roberto",900));
playerList.add(new Player("Albert",450));
playerList.add(new Player("Michel",750));
playerList.add(new Player("Mario",750));

// 1-Define how the sorting will be done in the Lambda Expression (Second parameter)
// 2-sort
Collections.sort(playerList, (p1, p2) -> p1.getScore().compareTo(p2.getScore()) );

// display the sorted list of players (ascendent order)
System.out.println(playerList);

The result of the execution will be as follow :

[[name : Albert, score : 450], [name : Messi, score : 500], [name : Ronaldo, score : 750], [name : Michel, score : 750], [name : Mario, score : 750], [name : Roberto, score : 900]]

 

Example of replacing anonymous inner class by a Lambda expression in Java 8

One  of the facilities that Java 8 offers is the reduction of the numbers of lines of code in some situations. In this example, we will replace an anonymous inner class by a Lamba Expression. Reducing the code used to compare two players to almost one single line.

First of all, we will need a Player object. A player has a name and a score. Please note that we will use the Integer wrapper type instead of the primitive int type, in order to benefit from the compareTo method offered by the Integer object.

package com.tutoref;

public class Player {
 
 private String name;
 private Integer score;
 
 // Default constructor to allow instanciation without parameters
 public Player(){}
 
 // Constructor with parameters
 public Player(String name, int score){
  this.name = name;
  this.score = score;
 }
 
 public String getName() {
  return name;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 public Integer getScore() {
  return score;
 }
 
 public void setScore(Integer score) {
  this.score = score;
 }
 
 
 @Override
 public String toString() {
  // To customise the displaying of a player
  return "[name : "+this.name+", score : "+this.score+"]";
 }
}

 

Comparison using an anonymous inner class

The playerComparator inner class is used to tell the program how to compare two players, based on their score.

In this example we create the playerComparator of type Coparator<Player>, after that create and add some players to the ListArray collection, then we use the Collections.sort method ot sort the players based on their score.

package com.tutoref;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class PlayerSort {

 public static void main(String[] args) {
  
  // Create a comparator object
  Comparator<Player> playerComparator = new Comparator<Player> () {
   @Override
   public int compare(Player player1, Player player2) {
    return player1.getScore().compareTo(player2.getScore());
   }
  };
  
  
  // Create a list of players
  List<Player> playerList = new ArrayList<Player>();
  
  // add some players
  playerList.add(new Player("Messi",500));
  playerList.add(new Player("Ronaldo",750));
  playerList.add(new Player("Roberto",900));
  playerList.add(new Player("Albert",450));
  playerList.add(new Player("Michel",750));
  playerList.add(new Player("Mario",750));
  
  // Sort the list based on the score 
  // using the comparator object
  Collections.sort(playerList,playerComparator);
  
  // display the sorted list of players (in ascendant order)
  System.out.println(playerList);
 }

}

 

COMPARISON Using Lambda Expression

This example will replace the  anonymous inner class, reducing it to one line of code.

package com.tutoref;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class PlayerSortUsingLambdaExp {

 public static void main(String[] args) {
  
  // Create a list of players
  List<Player> playerList = new ArrayList<Player>();
  
  // add some players
  playerList.add(new Player("Messi",500));
  playerList.add(new Player("Ronaldo",750));
  playerList.add(new Player("Roberto",900));
  playerList.add(new Player("Albert",450));
  playerList.add(new Player("Michel",750));
  playerList.add(new Player("Mario",750));
  
  Collections.sort(playerList, (p1, p2) -> p1.getScore().compareTo(p2.getScore()) );
  
  // display the sorted list of players (ascendent order)
  System.out.println(playerList);
 }

}

The compactor interface is also a Functional Interface, this is why it can be replaced by a lambda expression. You can imagine that we passed the function making the comparison as parameter.