Lambda Expressions & Functional Interface
Lambda Expression is just an anonymous (nameless) function.
- A function does not have a state.
- Object has a state.
Best Practices for Functional Programming
- Be declarative and less imperative
- Favor immutability
- Reduce side effects
- Expressions over statements
- 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

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

even the argument/parameter data type can removed as

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;
}
}