Lambda Expressions in Java

2013, Sep 01    

Introduction:

          Based on JSR 335, OpenJDK started project lambda to build a prototype which can make the Java language support Lambda Expressions. So in short we can say that Lambda Expression is a feature newly added in Java 8 to fulfill JSR 335. Basically it is introduced as a short form replacement of Anonymous Inner Class.

Why To Use It?

          It has the following benefits over Anonymous Inner Classes which makes us to use this:
  •   Its syntax is very neat and concise.
  •   Its very easy to use and make the program more readable.

Prerequisites:

          As it is a part of upcoming Java 8, you need to download the JDK 8 from this link.

          Before giving any examples on the usage of Lambda Expressions, we should be very clear about some of the terminologies used in this article.

Functional Interface:

          The interfaces with one abstract method are called as Functional Interface. Lambda Expressions can only used with Functional Interfaces. The code snippet shows an example of a Functional Interface:



public interface SayHello {
String say(String message);
}


          As interface SayHello has only one abstract method. So SayHello can be called as a Functional Interface.

          Functional Interfaces can also contain more than one methods, but the other methods should and must be the methods defined in java.lang.Object class. Here is another example of Functional Interface:


public interface SayHello {
String say(String message);
String toString();
}


          As the toString() method is defined in java.lang.Object class, so we can say the above interface as a Functional Interface.

Target Type:

          The Functional Interface for which the Lambda Expression is invoked is called as the target type of the Lambda Expression.

          For example: If a Lambda Expression is invoked for SayHello interface, then SayHello is the target type of that Lambda Expression.

          We can say that the type of any Lambda Expression is determined by Java compiler using the Target Type of the context. So as a simple thumb rule we can conclude that Lambda Expressions can be used only in those situations where Java compiler can determine the Target Type.

          Below are the areas where we can use Lambda Expressions:
  •     Variable Declarations
  •     Assignments
  •     Return Statements
  •     Array Initializers
  •     Method or Constructer Arguments
  •     Lambda Expression Bodies
  •     Conditional Expressions
  •     Cast Expressions
          As we move forward we will know how to use Lambda Expression in the given areas.

Syntax of Lambda Expressions:

          Below is the syntax of a Lambda Expression:

(formal parameter list) -> {expression or statement(s)}

          In short we can say that it has three parts:
  1.     An Arrow (->) symbol.
  2.     A list of formal parameters to the left side of Arrow.
  3.     Body part of it to the right side of the Arrow.
          Here is the 1st example demonstrating the use of Lambda Expression:


public class FirstExample {

interface SayHello {
String say(String message);
}

public static void main(String[] args) {

//Saying hello through anonymous class
SayHello helloAnonymous = new SayHello() {

@Override
public String say(String message) {
return "Hello " + message;
}
};
System.out.println(helloAnonymous.say("Annonymous Class"));

//Saying hello through lambda expression
SayHello helloLambdaExpr = (String message) -> "Hello " + message;
System.out.println(helloLambdaExpr.say("Lambda Expression"));
}
}


          From the above example, its very clear that the syntax is very neat and concise as compare to anonymous class.

          We need to follow some rules in order to use Lambda Expressions in our programs. Each rule will be explained with a simple example. Let's first understand the rules related to the syntax of Lambda Expression.

Rules for Formal Parameters of Lambda Expression:

RULE 1: 

          If the abstract method of functional interface is a zero argument method, then the left hand side of the arrow(->) we must use an empty parentheses.


public class SecondExample {

interface StaticMessage {
String say();
}

public static void main(String[] args) {

//Observe the empty parentheses
StaticMessage msg = () -> "A Fixed Message";
System.out.println(msg.say());
}
}

RULE 2:

          If the abstract method of functional interface is an one argument method, then the parentheses is not mandatory.


public class ThirdExample {

interface SayHello {
String say(String message);
}

public static void main(String[] args) {
SayHello sh = message -> "Hello " + message;
System.out.println(sh.say("Lambda Expression"));
}
}

          If you closely observe the above example, you can find that the type of the formal parameter of lambda expression is not mentioned.

NOTE: When we omit parentheses, we must omit the type of the formal parameter. Otherwise syntax error will be generated.

RULE 3:

          If the abstract method of functional interface is an multiple argument method, then the parentheses is mandatory.
          Here the formal parameters should be comma separated and should be in the same order of the corresponding functional interface.


public class FourthExample {

interface SayHello {
String say(String message, String name, Sex sex);
}

enum Sex {
MALE, FEMALE
}

public static void main(String[] args) {
SayHello sh = (String msg, String name, Sex sex) -> {
if(sex == Sex.MALE) {
return "Hello Mr. " + name + ", " + msg;
} else {
return "Hello Ms. " + name + ", " + msg;
}
};
System.out.println(sh.say("Good Morning!!!", "Ram", Sex.MALE));
System.out.println(sh.say("Good Morning!!!", "Sita", Sex.FEMALE));
}
}

RULE 4:

          Mentioning type of the formal parameters are not mandatory. In case you have not mentioned the type of formal parameters, then it's type will be determined by the Java compiler from the corresponding Target Type.
          Lambda expression used in the above example can be rewritten as:


SayHello sh = (msg, name, sex) -> {
if(sex == Sex.MALE) {
return "Hello Mr. " + name + ", " + msg;
} else {
return "Hello Ms. " + name + ", " + msg;
} };

CAUTION: If we want to omit the type of formal parameters, then we we have to omit it from all the parameters. Mentioning the type of some parameter and omitting from some parameter will raise compile time error.

Rules for Body of Lambda Expression:

RULE 1:

          The body of a lambda expression can either be a single expression or one or more statements.


public class FifthExample {

interface SayHello {
String hello(String name);
}

public static void main(String...args) {

//Saying hello through lambda expression using single expression
SayHello sh1 = msg -> "Hello " + msg;
System.out.println(sh1.hello("Lambda Expression With Expression"));

//Saying hello through lambda expression using statement
SayHello sh2 = msg -> { return "Hello " + msg; };
System.out.println(sh2.hello("Lambda Expression With Statement"));

//With multiple statements
SayHello sh3 = msg -> {
String hello = "Hello " + msg;
return hello;
};
System.out.println(sh3.hello("Lambda Expression With Multiple Statement"));
}
}

RULE 2:

          If we are using a single expression as body of any lambda expression, then we cannot enclose the body with curly braces({}).
          In this case the evaluated value of the expression will be the return value of lambda expression. So the return statement should not be used. Because a return statement is not an expression.


SayHello sh2 = msg -> {"Hello " + msg }; // Compile time error.
SayHello sh = msg -> "Hello " + msg; // Runs fine

RULE 3:

          If we are using one or more statements as body of any lambda expression, then enclosing them within curly braces({}) is mandatory.
          Here return statement is mandatory, if the method of the functional interface has nay return type.


SayHello sh = msg -> {String hello = "Hello " + msg; }; // Compile time error, No return statement
SayHello sh = msg -> {String hello = "Hello " + msg; return hello; }; // Runs fine

Local variables in Lambda Expressions:

          These are the following rules we need to follow in case of local variables in lambda expressions.

RULE 1:

          Lambda Expressions doesn't define any new scope like what anonymous inner class does, so we can not declare a local variable with same which is already declared in the enclosing scope of the lambda expression.


public class SixthExample {

interface Algebra {
int add(int a);
}

public static void main(String[] args) {

// Declaring a variable
int x = 10;
Algebra alg = a -> {
int x = 20;
return a + x;
};
System.out.println("10 + 20 = " + alg.add(10));

// Above will generate error saying variable x is already defined in main(String[])
// Because lambda expression doesn't define a new scope.
// But anonymous class does, so comment the above one and
// try the below one, it will work fine

// Using Anonymous class
Algebra alg1 = new Algebra() {

@Override
public int add(int a) {
int x = 20;
return a + x;
}
};
System.out.println("10 + 20 = " + alg1.add(10));
}
}

RULE 2:

          Inside Lambda Expression, we cannot assign any value to some local variable declared outside the lambda expression. Because the local variables declared outside the lambda expression (inside the enclosing scope of the lambda expression) should be final or effectively final.
          Here effectively final means we can only use those variables but we cannot modify those inside lambda expression.


public static void main(String[] args) {

// Declaring a variable
int x = 10;
Algebra alg = a -> {
x = 20;
return a + x;
};
System.out.println("10 + 20 = " + alg.add(10));

// Above will generate error saying local variable referenced from
// lambda expression must be final or effectively final
}

RULE 3:

          The rule of final or effectively final is also applicable for method parameters and exception parameters.


public class SeventhExample {

interface Algebra {
int add(int a);
}

public static void main(String[] args) {
System.out.println("10 + 10 = " + getResult(20));
}

static int getResult(int x) {
Algebra alg = a -> {
x = 10; //This will generate the same error as above case
return a + x;
};
return alg.add(10);
}
}

RULE 4:

          this and super references inside the lambda body are same as their enclosing scope. Because we know lambda expressions doesn't define any new scope.


public class EighthExample {

int x = 10;

interface SayHello {
String say(String message);
}

public static void main(String[] args) {
EighthExample obj = new EighthExample();
obj.show();
}

public void show() {
SayHello sh = new SayHello() {
@Override
public String say(String message) {
System.out.println(this.x); //Compile time error, x cannot be resolved
return "Hello " + message;
}
};
System.out.println(sh.say("Anonymous"));

SayHello sh1 = msg -> {
System.out.println(this.x); //Prints 10
return "Hello " + msg;
};
System.out.println(sh1.say("Lambda"));
}
}

Exception Handling in Lambda Expression:

          Following are the rules we need to follow in order to use exception handling mechanism of Java.

RULE 1:

          Lambda expression cannot throw any checked exception until its corresponding functional interface declares a throws clause.


public class NinthExample {

interface ThrowException {
void throwing(String message);
}

public static void main(String[] args) {
ThrowException te = msg -> {
throw new Exception(msg);
//Cannot be thrown as throwing(String)
//doesn't define any throws clause
//Now comment the above one and rerun,
//it will work fine as RuntimeException
//is not checked exception
throw new RuntimeException(msg);
};
te.throwing("Lambda");
}
}

RULE 2:

          The exception thrown by any lambda expression should be of same type or sub-type of the exception type declared in the throws clause of its functional interface.


public class TenthExample {

interface ThrowException {
void throwing(String message) throws IOException;
}

public static void main(String[] args) throws Exception {

ThrowException te = msg -> {
//Runs fine
throw new IOException(msg);
//Compile time error. As Exception is not the sub-type of IOException
throw new Exception(msg);
//Runs fine as FileNotFoundException is a sub-type of IOException
throw new FileNotFoundException(msg);
};
te.throwing("Lambda");
}
}

Lambda Expression in return Statement:


public class ReturnExample {

interface Algebra {
int add(int a, int b);
}

public static Algebra getAlgebra() {
return (a, b) -> a + b;
}

public static void main(String...args) {
System.out.println("10 + 20 = " + getAlgebra().add(10, 20));
}
}

Lambda Expression in Array Initializers:


public class ArrayInitExample {

interface Algebra {
int operate(int a, int b);
}

public static void main(String[] args) {
Algebra al[] = new Algebra[] {
(a, b) -> a+b,
(a, b) -> a-b,
(a, b) -> a*b,
(a, b) -> a/b
};
System.out.println("10 + 20 = " + al[0].operate(10, 20));
System.out.println("10 - 20 = " + al[1].operate(10, 20));
System.out.println("10 * 20 = " + al[2].operate(10, 20));
System.out.println("10 / 20 = " + al[3].operate(10, 20));
}
}

Lambda Expression as Method or Constructor Arguments:


public class MethodArgExample {

interface Algebra {
int operate(int a, int b);
}

enum Operation {
ADD, SUB, MUL, DIV
}

public static void main(String[] args) {
printValue((a, b) -> a + b, Operation.ADD);
printValue((a, b) -> a - b, Operation.SUB);
printValue((a, b) -> a * b, Operation.MUL);
printValue((a, b) -> a / b, Operation.DIV);
}

static void printValue(Algebra a, Operation op) {
switch (op) {
case ADD:
System.out.println("10 + 20 = " + a.operate(10, 20));
break;
case SUB:
System.out.println("10 - 20 = " + a.operate(10, 20));
break;
case MUL:
System.out.println("10 * 20 = " + a.operate(10, 20));
break;
case DIV:
System.out.println("10 / 20 = " + a.operate(10, 20));
break;
default:
throw new AssertionError();
}
}
}

Lambda Expression in Conditional Expression:



public class ConditionalExprExample {

interface Algebra {
int substract(int a, int b);
}

public static void main(String...args) {
System.out.println(getAlgebra(false).substract(10, 20));
System.out.println(getAlgebra(true).substract(20, 10));
}

static Algebra getAlgebra(boolean reverse) {
Algebra al = reverse ? (a, b) -> a - b : (a, b) -> b - a;
return al;
}
}

Lambda Expression in Casting Expression:


public class CastingExample {

interface Algebra {
int operate(int a, int b);
}

interface Algebra1 {
int operate(int a, int b);
}

public static void main(String[] args) {
printResult((a, b) -> a + b);
}

static void printResult(Algebra a) {
System.out.println("From Algebra Interface");
System.out.println(a.operate(10, 20));
}

static void printResult(Algebra1 a) {
System.out.println("From Algebra1 Interface");
System.out.println(a.operate(10, 20));
}
}

          The above example will generate an ambiguous error, because both the printResult() methods are valid. So to avoid this type of situations you need to type cast the lambda expression as follows:


public static void main(String...args) {
printResult((Algebra)(a, b) -> a + b); //Calls printResult(Algebra)
printResult((Algebra1)(a, b) -> a + b); //Calls printResult(Algebra1)
}

Conclusion:

          As the examples above shown tells that it is a short form replacement of anonymous inner class, we should not assume that it will replace the anonymous class. Because you should remember very carefully that lambda expression will only work with respect to functional interfaces whereas anonymous class can work with any type of interfaces.

References:

          Lambda Expressions by Deepak Vohra

Feedback:

          Anybody who finds any kind of mistakes in this article, please let me know either through comment or mail.