Thursday, June 15, 2023

Lambda Expressions in Java 8

Lambda expressions in Java (also known as closures) are one of the most important feature added in Java 8. Java lambda expressions provide a clear and elegant way to represent a single abstract method interface (Functional interface) using an expression.


What is a functional interface

In order to know Lambda expressions better it is very important to have a good understanding of two terms- lambda expression itself and functional interface. Here is a little primer about functional interfaces.

A functional interface is an interface with only one abstract method. A functional interface is also known as SAM type where SAM stands for (Single Abstract Method). An example of functional interface would be Runnable interface which has only one method run().

Please note that from Java 8 it is possible for an interface to have default methods and static methods thus the stress on "only one abstract method".

Also a new annotation @FunctionalInterface has been added in Java 8, all the functional interfaces can be annotated with this annotation to ensure that they have only single abstract method.

functional interface Example

interface IMyInterface {
  int getValue();
}

In interface IMyInterface there is a single abstract method getValue() (note that in an interface methods are implicitly abstract).

See Functional Interfaces in Java for detailed post about functional interfaces.

Java lambda expressions

Lambda expression in Java is an instance of a functional interface and provides implementation of the single abstract method defined by the functional interface.

Lambda Expression Syntax

A new operator has been introduced in Java for Lambda expressions.

(arg1, arg2, ..) -> body

Here you can see a new arrow operator (->) or lambda operator which divides the lambda expression into two parts.

Left side specifies parameters required by the lambda expression. Parameters are optional, if no parameters are needed an empty parenthesis can be given. Even type of the parameters is not required as compiler, in most of the cases, is able to infer type of the parameters from the context in which it is used.

Right side known as lambda body specifies the logic of the lambda expression.

Why Lambda expressions in Java

Now when we have an idea about lambda expressions and functional interfaces let's try to understand how does lambda expressions help in making your code more compact.

Most used use case for anonymous class is to implement an interface that contains only one method. This is usually done when you are trying to pass functionality as an argument to another method.

For example if you have a List of objects of type Person and you want to sort them on the basis of first name using a Comparator. In that case you need to implement compare() method of the Comparator interface. Usually that is done by implementing Comparator as an anonnymous class. So what you are doing is to pass the functionality to compare objects as an argument to Collections.sort() method.

Collections.sort(personList, new Comparator<Person>() {
  public int compare(Person a, Person b) {
    return a.getFirstName().compareTo(b.getFirstName());
  }
});
Java lambda expression also enables you to do the same thing like-
  • Passing functionality as method argument.
  • Passing code as data.

Lambda expressions can only be used to implement functional interfaces (interface with single abstract method) and lambda expressions let you express these instances of single-method classes more compactly than by using anonymous class.

For example if you have to implement the same Comparator as used above as a lambda expression then it can be done more compactly as-

Collections.sort(personList, (Person a, Person b) -> 
            a.getFirstName().compareTo(b.getFirstName()));
Type can be inferred from the surrounding context so you don't even need to define the type of the parameters.
Collections.sort(personList, (a, b) -> 
            a.getFirstName().compareTo(b.getFirstName()));

Here you can see that the functionality for sorting (implementation of compare method of the Comparator) is passed as method argument.

Java lambda expression examples

Let's see some examples of the lambda expressions in Java-

1. A very simple lambda expression.

() -> 7; 

This lambda expression takes no parameter, that's why empty parenthesis and returns the constant value 7.

If we have to write the equivalent Java method then it will be something like this-

int getValue(){
  return 7;
}

2. Lambda expressions with 2 parameters.

// concatenating two strings, it has 2 string parameters
// and they are concatenated in lambda body
(String s1, String s2) -> s1+s2;

3. Lambda expression to test if the given number is odd or not.

// Lambda expression to test if the given number is odd or not
n -> n % 2 != 0;

4. Lambda expression to display passed argument.

// Prints the passed string s to the console and returns void
(String s) -> { System.out.println(s); };

5. Complete Java Lambda expression example.

interface IMyInterface {
  int getValue();
}

public class LambdaExpressionDemo {
  public static void main(String[] args) {
    // reference of the interface
    IMyInterface objRef;
    // Lambda expression
    objRef = () -> 7;
    System.out.println("Value is " + objRef.getValue());
    // Another lambda expression using the same interface reference 
    objRef = () -> 7 * 5;
    System.out.println("Value is " + objRef.getValue());
    // This line will give compiler error as target type 
    // can't be inferred 
    objref = () -> "11";
  }
}

Output

Value is 7
Value is 35

Here we have an interface IMyInterface with one method getValue() whose return type is int. Since there is only one abstract method in the interface so IMyInterface is a functional interface.

In this program it can be seen how lambda expression () -> 7 is compatible with the abstract method. Return type is int and there is no method parameter.
If you uncomment the line () -> "11"; it will give compiler error The target type of this expression must be a functional interface as in this case return type is string which is not compatible with the return type of the abstract method, lambda expression is implementing.

Also notice that the same functional interface reference is used to execute 2 lambda expressions
objRef = () -> 7; and objRef = () -> 7 * 5;
Since both of the lambda expressions are compatible with the target type so both of them can be assigned to the same reference (Does it remind you of run time polymorphism?). That's why Lambda Expression is a Poly Expression.

6. Java Lambda expression with a parameter example.

interface IMyInterface {
  boolean test(int n);
}

public class LambdaExpressionDemo {
  public static void main(String[] args) {
    IMyInterface objRef;
    // Lambda expression
    objRef = n -> (n % 2) == 0;
    System.out.println("4 is even " + objRef.test(4)); 
    System.out.println("3 is even " + objRef.test(3)); 
  }
}

Output

4 is even true
3 is even false

Here we need to note certain points-

n -> (n % 2) == 0;

In this lambda expression type is not specified explicitly as int, type is inferred from the context in which the lambda expression is executed, though type can be given explicitly too.

int n -> (n % 2) == 0;

This lambda expression is also valid.

Also parenthesis around the parameter is omitted, in case of single parameter it is not necessary to enclose the parameter with parenthesis. Again it won't be invalid to do that, so (n) -> (n % 2) == 0; or (int n) -> (n % 2) == 0; both are valid.

In case of more than one parameter too type can be inferred so
(int x, int y) -> x+y; or (x, y) -> x + y;
both of these are valid if used in a correct context.

One important point here is we can't have lambda expression where type for only one of the parameter is explicitly declared.

// Invalid lambda expression
(int x, y) -> x + y; 

See Lambda Expression Examples in Java for more examples of lambda expression.

Target type of Lambda expressions in Java

Lambda expression is an instance of the functional interface thus the functional interface specifies its target type.

Lambda supports "target typing" which infers the object type from the context in which it is used. To infer that object type from the context-

  • The parameter type of the abstract method and the parameter type of the lambda expression must be compatible. For Example, if the abstract method in the functional interface specifies one int parameter, then the lambda should also have one int parameter explicitly defined or implicitly inferred as int by the context.
  • Its return type must be compatible with the method's type.
  • Lambda expression can throw only those exceptions which are acceptable to the method.

Target typing is used in a number of contexts including the following-

  • Variable declarations
  • Assignments
  • Return statements
  • Array initializers
  • Method or constructor arguments
  • Lambda expression bodies

Block lambda expression in Java

Till now all the examples we have seen are single expression lambda, we also have a second type of lambda expressions known as "block lambda" where the right side of the lambda expression is a block of code.

Let's see an example, here with in the lambda expression we have a block of code for counting the words in a string without using any String function.

@FunctionalInterface
interface IMyInterface {
  int doCount(String str);
}

public class LambdaExpressionDemo {
  public static void main(String[] args) {
    // Lambda expression
    IMyInterface objRef = (str) -> {
      int c = 0;
      char ch[]= new char[str.length()];
      for(int i = 0; i < str.length(); i++){
          ch[i] = str.charAt(i);
          if(((i > 0) && (ch[i] != ' ') && (ch[i-1] == ' ')) || 
          ((ch[0] != ' ') && (i == 0)))
              c++;
      }
      return c;
    };    
    System.out.println("Words in the string " + objRef.doCount("Lambda Expression in Java"));    
  }
}

Output

Words in the string 4

Note here that functional interface is annotated with a @FunctionalInterface annotation, this is a new annotation added in Java 8 to be used with functional interface.

See Functional interface annotation in Java 8 for detailed post about @FunctionalInterface annotation.

Points to note-

  • Lambda expression is an instance of a functional interface and provides implementation of the single abstract method defined by the functional interface.
  • The lambda expression signature must be the same as the functional interface method's signature, as the target type of the lambda expression is inferred from that context.
  • A lambda expression can throw only those exceptions or the subtype of the exceptions for which an exception type is declared in the functional interface method's throws clause.
  • The parameter list of the lambda expression can declare a vararg parameter: (int ... i) -> {};
  • A Lambda Expression Is a Poly Expression- The type of a lambda expression is inferred from the target type thus the same lambda expression could have different types in different contexts. Such an expression is called a poly expression.

That's all for this topic Lambda Expressions in Java 8. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Java Lambda Expression as Method Parameter
  2. Java Lambda Expression And Variable Scope
  3. Method Reference in Java
  4. Java Stream API Tutorial
  5. Java Lambda Expressions Interview Questions And Answers

You may also like-

  1. How to Fix The Target Type of This Expression Must be a Functional Interface Error
  2. How to Resolve Local Variable Defined in an Enclosing Scope Must be Final or Effectively Final Error
  3. Java Exception Handling Tutorial
  4. Multi-Catch Statement in Java Exception Handling
  5. Covariant Return Type in Java
  6. static Import in Java With Examples
  7. How HashMap Works Internally in Java
  8. Difference Between yield And sleep in Java Multi-Threading