Lambda Expressions & Functional Interface

4 minute read

Lambda Expression is just an anonymous (nameless) function.

  • A function does not have a state.
  • Object has a state.

Best Practices for Functional Programming

  1. Be declarative and less imperative
  2. Favor immutability
  3. Reduce side effects
  4. Expressions over statements
  5. Design with Higher-Order functions

Functional Interface

  • Interface with SAM : Single Abstract Method
  • Functional Interface : can automatically be elevated to lambda expression. In other words, you can only use lambdas for functional interfaces
  • Functional interface assign a contract!!
View Functional Interfaces

Writing Lambda

A function/method has 4 parts : name, return type, params/args list & method body

Parts of a Method/Function

The most important parts are just the arguments and body. In Functional interface, there is only one method, so the name of the method is implied and the override has to be done by any class implementing the method.

Lambda with parameter/argument data type Image Text

even the argument/parameter data type can removed as Image Text

Spotting Valid Lambdas

Defining Lambda : Providing implementation to the abstract method

y = (arg1, arg2) -> arg1 + arg2;
  • RETURN Always need curly braces and ends with a colon
  • Without Curly braces we can’t use return keyword
     n->{n*n;};//INVALID, no return statement.
      
     c -> return 10; //DOES NOT COMPILE : return keyword without {}
     c -> { return 10; } //CORRECT
       
    a -> {
        return a.startsWith("test")
        }//DOES NOT COMPILE : need ; after return
    a -> {
       return a.startsWith("test");
     }//CORRECT
    
  • multiple parameters need to be enclosed in the brackets
    a,b -> a.startsWith("Ni")//DOES NOT COMPILE : need small brackets
    CORRECT: (a,b) -> a.startsWith("Ni")
    
  • Data types for the input parameters of a lambda expression are optional
    (int y, z) -> { int x = 1; return x+y; }// DOES NOT COMPILE : Either both have data types or none
    ( y, z) -> { int x = 1; return x+y; } // CORRECT
     (int y, int z) -> { int x = 1; return x+y; }//CORRECT:
    
    (a,b) -> { int a = 9; return a+b }//DOES NOT COMPILE: Redeclaration of a
    (a,b) -> { int c = 9; return a+b }// CORRECT AS C is an independent local variable
    
  • VALID Lambdas
    MyFunctionalInterface t;
    t = () -> true; //ZERO Parameter, return Boolean
    t = a -> {return a.startsWith("Ni");}
    t = (String a) -> a.startsWith("Ni")
    t = (int x) -> {} //One parameter and no function body
    t = (int y) -> {return;}
    

Method Accepting Lambda

Any method that accepts Functional Interface as parameter, needs a Lambda, For Example forEach accepts a Consumer (a functional interface) as a parameter.

default void forEach(Consumer<? super T> action);

Traditionally the anonymous class implementation is done :-

List<Integer> list = Arrays.asList(1,4,6,8,9,7,5,3,2);

//Implemenation via anonymous inner class
list.forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer i) {
        System.out.println(i);
    }
});

Above can be reduced by just keeping only the args and the method body

list.forEach((Integer i) -> {
        System.out.println(i);
        return;
    });
//i -> params/args & System.out.println(i) -> body

This can further be reduced by removing data type from argument, and removing unnecessasary return statement

list.forEach(i -> System.out.println(i));

This can be further reduced with the usage of method reference

list.forEach(System.out::println);

For better understanding, step by step declaration and usage can be tried.

Consumer consumer = x -> System.out.println(x);//since for each accepts a consumer, declare it first
//use the consumer with the method present in the Consumer interface
list.forEach(val -> consumer.accept(val));
//Or for simplicity just pass the consumer
list.forEach(consumer);
//or just replace the variable
list.forEach(x -> System.out.println(x));

Strategy Pattern

Strategy pattern. writing a function to be called as Lambda

Consider a method written in such a way that it accepts a functional interface as an argument.

Just pass a Lambda as parameter

Since the strategy can be decided at runtime, we can pass the strategy right at the time when its needed

List<Integer> values = Arrays.asList(1, 2, 3, 4, 5, 6);

//Print sum of all numbers
System.out.println(totalValues(values, e -> true));

//Print sum of all all Even numbers
System.out.println(totalValues(values, e -> e % 2 == 0));

//Print sum of all odd numbers
System.out.println(totalValues(values, e -> e % 2 != 0));

Lambda expression can access

  • static variables,
  • instance variables,
  • effectively final variables and
  • effectively Final local variables

Effectively Final Variables

A local variable is effectively final if it is assigned once and never reassigned. Lambdas and anonymous classes can capture such variables because they are stable values.

Example

import java.util.function.IntPredicate;

public class EffectivelyFinalDemo {
    public static void main(String[] args) {
        int threshold = 10; // assigned once -> effectively final

        IntPredicate isGreaterThan = n -> n > threshold;
        System.out.println(isGreaterThan.test(12)); // true

        // threshold = 15; // DOES NOT COMPILE: variable used in lambda must be final or effectively final
    }
}

Why this rule exists: captured locals are copied into the lambda at creation time. If the local could change later, the lambda would see a stale value, so Java enforces “final or effectively final” for safety and clarity

Examples for Captured Variables

import java.util.function.IntSupplier;

public class LambdaCaptureDemo {
    private static int staticBase = 100; // static variable
    private int instanceBase = 20;       // instance variable

    public IntSupplier staticExample() {
        return () -> staticBase + 1; // can access static variable
    }

    public IntSupplier instanceExample() {
        return () -> instanceBase + 1; // can access instance variable
    }

    public IntSupplier effectivelyFinalExample() {
        int bonus = 5; // effectively final local variable
        return () -> instanceBase + bonus; // bonus is captured
    }

    public IntSupplier effectivelyFinalLocalExample() {
        int offset = 3; // effectively final
        IntSupplier supplier = () -> offset * 2;
        // offset = 7; // DOES NOT COMPILE: variable used in lambda must be final or effectively final
        return supplier;
    }
}