Lambda Expressions

Lambda Expressions

Lambda Expression is an anonymous function. i.e: a function that does not have any name.

Writing Lambda Expression:

Syntax:

    ( Arguments list ) -> { Body of Lambda Expression }
  1. Argument list : There can be zero or more arguments.

  2. Arrow-token : The arrow token separates the parameters from the body of the lambda expression.

  3. Body of Lambda Expression : The body of the lambda expression contains the code to be executed.

Examples:

Java MethodEquivalent Lambda Expression
public void greet() {

System.out.println("Hello World");
} | () -> { System.out.println("Hello World"); } | | public void sum(int a, int b) {
System.out.println(a+b);
} | (int a, int b) -> { System.out.println(a+b); } | | public String convertToUpperCase(String s) {
return s.toUpperCase();
} | (String s) -> { return s.toUpperCase(); } |

Java Method to find length of the string :

   public int getLength(String s) {
       return s.length();
   }

Equivalent Lambda Expression :

   (String s) -> { return s.length(); }

Optimizing Lambda Expressions:

  1. If the lambda expression consists of only one statement, then the curly braces of the code-block are optional.
    i.e:

         (String s) -> return s.length();
    
  2. The type of parameters/arguments can be automatically inferred by the compiler based on context (type inference) so, mentioning types of parameters is also optional.
    i.e:

         (s) -> return s.length();
    
  3. The return type can also be inferred by compiler automatically (type inference) so, mentioning return type is also optional.
    But for this the lambda expression must contain only one statement.
    i.e:

        (s) -> s.length();
    
  4. If there is only one parameter in Lambda Expression the parameter brackets can also be removed.
    i.e:

         s -> s.length();
    
Lambda ExpressionsEquivalent Optimized Versions
() -> { System.out.println("Hello World"); }() -> System.out.println("Hello World");
(int a, int b) -> { System.out.println(a+b); }(a, b) -> System.out.println(a+b);
(String s) -> { return s.toUpperCase(); }s -> s.toUpperCase()

Note:
Once the lambda expression is written, we can call/invoke that expression just like normal methods but for this Functional interface instance is required.

Functional Interface reference is used to refer lambda expression. It acts as a type for Lambda Expression.
The lambda expression gets mapped to the
Single abstract method of the Functional interface, and the parameter type and return type is inferred from its method signature. (type inference)
It can be invoked by invoking the SAM(Single Abstract Method) of Functional Interface.


Concrete implementation vs Anonymous class implementation vs Lambda Expression implementation of Functional Interface.

Concrete class implementation:

@FunctionalInterface
public interface Interf{
    void methodOne();
}

// Concrete implementation
public class Demo implements Interf{
    public void methodOne(){
        System.out.println("Method one execution using simple class implementation");
    }
}

public class App{
  public static void main(String[] args) {
    Interf i = new Demo();
    i.methodOne();
  }
}

Output:

Method one execution using simple class implementation

Anonymous Class implementation:

@FunctionalInterface
public interface Interf {
    void methodOne();
}

public class App {
    public static void main(String[] args) {
        //Anonymous class
        Interf i = new Interf() {
            @Override
            public void methodOne() {
                System.out.println("Method one execution using anonymous class implementation");
            }
        };
        i.methodOne();
    }
}

Output:

Method one execution using anonymous class implementation

Lambda Expression implementation:

public interface Interf{
    void methodOne();
}

public class App{
    public static void main(String[] args) {
        // Functional Interface
        Interf i = () -> System.out.println("Method one execution using Lambda expression");
        i.methodOne();
    }
}

Output:

Method one execution using Lambda expression

Anonymous Inner Classes vs Lambda Expressions:

Lambda Expressions can be easily mistaken as a replacement for Anonymous class implementation, but it's not true.

  • An Anonymous inner class can extend a concrete class.

      class Test{
          ...
      }
    
      class App{
          public static void main(String[] args) {
              Test t = new Test(){
                  ...
              };
          }
      }
    
  • An Anonymous inner class can extend an abstract class.

      abstract class Test{
          ...
      } 
    
      class App{
          public static void main(String[] args) {
              Test t = new Test(){
                  ...
              };
          }
      }
    
  • An Anonymous inner class can implement an interface containing multiple abstract methods.

      interface Test{
          void m1();
          void m2();
          void m3();
      }
    
      class App{
          public static void main(String[] args) {
              Test t = new Test(){
                  public void m1(){
                      ...
                  }
                  public void m2(){
                      ...
                  }
                  public void m3(){
                      ...
                  }
              };
          }
      }
    
  • An Anonymous inner class can implement an interface containing a single abstract method (Functional Interfaces).
    Lambda Expression can be used for this case only.

      interface Test{
          void m1();
      }
    
      class App{
          public static void main(String[] args) {
              //using anonymous inner class
              Test t1 = new Test(){
                  public void m1(){
                      ...
                  }
              };
              //using lambda expression
              Test t2 = () -> {
                  ...
              };
          }
      }
    

this keyword in Anonymous inner class and Lambda expression


A lambda expression can be treated as a simple code block while considering the variable scoping. So, all the rules for code blocks also apply to the lambda expressions.
Thus, the scope of a variable declared inside the lambda expression is restricted to the lambda expression only, it can't be accessed outside the lambda expression. But, the enclosing class variables can be accessed by the lambda expression.

Inside Lambda expression instance variables can't be declared, whatever variables are declared are simply local variables.

For the Anonymous class this keyword refers to the anonymous class object, while for lambda expression this keyword refers to the enclosing class object;


@FunctionalInterface
interface ValuesWrapper{
    void printValues();
}

public class App {
    String value = "enclosing class instance value";

    public ValuesWrapper getLambdaImpl(){
        ValuesWrapper lambdaImpl = () -> {
            String value = "lambda local value";
            System.out.println("Lambda implementation: ");
            System.out.println("value: "+value);
            System.out.println("this.value: "+this.value);
        };
        return lambdaImpl;
    }

    public ValuesWrapper getAnonymousImpl(){
        ValuesWrapper anonymousImpl = new ValuesWrapper(){
            String value = "anonymous class instance value";
            @Override
            public void printValues() {
                String value = "anonymous class local value";
                System.out.println("Anonymous calss implementation: ");
                System.out.println("value: "+value);
                System.out.println("this.value: "+this.value);
            }
        };
        return anonymousImpl;
    }

    public static void main(String[] args) {
        App app = new App();

        ValuesWrapper lambdaImpl = app.getLambdaImpl();
        lambdaImpl.printValues();

        System.out.println("==============================");

        ValuesWrapper anonymousImpl = app.getAnonymousImpl();
        anonymousImpl.printValues();
    }
}

Output:

Lambda implementation: 
value: lambda local value
this.value: enclosing class instance value
==============================
Anonymous calss implementation: 
value: anonymous class local value
this.value: anonymous class instance value

During compilation, the lambda expression gets converted into a private method of the enclosing class. That's why this keyword refers to the enclosing class object.

Anonymous Inner ClassLambda Expression
It's a class without a nameIt's a method without a name (Anonymous function)
Anonymous inner class can extend abstract class as well as concrete class.Lambda Expression can't extend abstract class or concrete class.
Anonymous inner class can implement an interface that contains any number of abstract methods.Lambda Expression can be used only for an interface which contains single abstract method (Functional Interface).
Inside anonymous inner class we can declare instance variables.Inside Lambda expression we can't declare instance variables, whatever the variables are declared simply acts as local variables.
Anonymous inner classes can be instantiated.Lambda expressions can't be instantiated.
Inside Anonymous inner class "this" always refers to current inner class object not outer class object.Inside Lambda expressions "this" always refers to current outer class object; i.e.- enclosing class object.
At the time of compilation a separate .class file is generated for Anonymous inner class. (OuterClass$1.class)At the time of compilation no extra .class file will be generated for lambda expression. It is simply converted into private method of outer class.

A lambda expression can't define any new scope as an anonymous inner class does, so we can't declare a local variable with the same name which is already declared in the enclosing scope of a lambda expression.

Conclusion:
Lambda Expressions and Annonymous inner classes are not same.

Lambdas and Local Variables (Closure)


The effectively final variables refer to local variables that are not declared final explicitly but can't be changed once initialized.
A lambda expression can use a local variable in outer scopes only if they are effectively final. such lambdas are referred as capturing lambdas.
They can capture static variables, instance variables, and local variables, but only local variables must be final or effectively final.

public class ClosureExample {
    public static void main(String[] args) {
        int x = 10; // Local variable

        // Lambda expression
        Runnable lambda = () -> {
            // Attempting to modify the local variable will result in a compilation error
            // x = 20; // Uncommenting this line will cause a compile-time error
            System.out.println("Value of x: " + x);
        };

        // Executing the lambda
        lambda.run();
    }
}

Reason for prohibited modification : When a lambda expression is created, it makes a copy of any variables from the surrounding scope that it refers to. If these variables were allowed to be modified, the lambda expression would hold outdated values from the copy, leading to inconsistencies. Conversely, if the copy within the lambda expression is modified, the value in the enclosing scope would remain unchanged, resulting in an inconsistency as well.

Another consideration is that when you pass around a lambda expression and it escapes to be executed by a different thread while the current thread is updating the same local variable, concurrency issues can arise.

To prevent such situations, the modification of local variables refereed by lambda expressions is prohibited.