Lambda Expressions & Functional Interfaces Masterclass
Before Java 8, Java was strictly object-oriented โ to pass behavior (code) into a method, developers had to write bulky, verbose Anonymous Inner Classes. The introduction of Lambda Expressions and Functional Interfaces in Java 8 revolutionized the language, introducing Functional Programming (FP) capabilities. Lambdas allow you to treat functionality as a method argument, write clean declarative code pipelines, and power the Stream API. In this comprehensive masterclass guide, you will master functional interfaces (Predicate, Consumer, Function, Supplier, BiFunction), method references (::), collection iterations, comparator sorting pipelines, and the golden effectively final variable capture rule.
Functional Programming (FP) ante functions ni First-Class Citizens ga treat cheyyadam โ ante functions ni arguments ga pass cheyyavachu, return cheyyavachu, mariyu variables lo store cheyyavachu. Java lo Lambda Expression ante Anonymous Function (peru leni function):
(parameter1, parameter2) -> { body / expression }
โโโโโโโโโโโฌโโโโโโโโโโโโโ โ โโโโโโโโโโโโฌโโโโโโโโโ
โ โ โ
Parameters Arrow Token Execution Logic
Examples:
1. Zero Params: () -> System.out.println("Hello");
2. Single Param: name -> System.out.println(name); (Parentheses optional!)
3. Multiple: (a, b) -> a + b; (Implicit return!)
4. Multi-line: (a, b) -> { int sum = a + b; return sum * 2; };
๐ก Imperative vs Functional Style (Anonymous Class vs Lambda)
Old Java (Anonymous Inner Class):
Runnable r = new Runnable() { public void run() { System.out.println("Run"); } }; (5 lines of boilerplate!)
Modern Java (Lambda Expression):
Runnable r = () -> System.out.println("Run"); (Clean 1-liner!)
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = List.of("Ravi", "Anu", "Kiran");
// Lambda Expression passed to forEach (Consumer)
names.forEach(name -> System.out.println(name));
}
}
A Functional Interface is an interface that contains EXACTLY ONE Single Abstract Method (SAM). It can have any number of default or static methods with concrete implementation, but only 1 abstract method. Lambda expressions can ONLY be assigned to Functional Interfaces:
@FunctionalInterface
interface MathOperation {
int operate(int a, int b); // The Single Abstract Method (SAM)
// Default methods are allowed!
default void printInfo() {
System.out.println("Executing math operation...");
}
}
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// Lambda implementing the SAM
MathOperation addition = (a, b) -> a + b;
MathOperation multiplication = (a, b) -> a * b;
System.out.println("10 + 20 = " + addition.operate(10, 20)); // 30
System.out.println("10 * 20 = " + multiplication.operate(10, 20)); // 200
}
}
Java standard library java.util.function package lo enterprise applications ki avasaramaina 5 core functional interfaces provide chesindi:
| Interface | Abstract Method | Input $ ightarrow$ Output | Key Purpose & Real-World Use |
|---|---|---|---|
Predicate<T> |
boolean test(T t) |
T $
ightarrow$ boolean |
Condition checking, filtering items in Streams (e.g. user.getAge() >= 18). |
Consumer<T> |
void accept(T t) |
T $
ightarrow$ void |
Consuming data / side-effects (e.g. System.out.println, sending emails). |
Function<T, R> |
R apply(T t) |
T $
ightarrow$ R |
Data transformation & mapping (e.g. converting String to Integer length). |
Supplier<T> |
T get() |
none $
ightarrow$ T |
Factory generator, lazy loading, creating UUIDs, generating timestamps. |
BiFunction<T, U, R> |
R apply(T t, U u) |
(T, U) $
ightarrow$ R |
Taking 2 arguments of different types and returning a computed result R. |
import java.util.function.*;
public class CoreInterfacesDemo {
public static void main(String[] args) {
// 1. Predicate: Check if number is even
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println("Is 14 even? " + isEven.test(14)); // true
// 2. Consumer: Print formatted string
Consumer<String> greeter = name -> System.out.println("Namaste, " + name + "!");
greeter.accept("Ravi"); // Namaste, Ravi!
// 3. Function: Convert string to character length
Function<String, Integer> stringLength = str -> str.length();
System.out.println("Length of 'Java 21': " + stringLength.apply("Java 21")); // 7
// 4. Supplier: Provide current timestamp
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println("Random Value: " + randomSupplier.get());
// 5. BiFunction: Combine Name and Salary into Employee Record String
BiFunction<String, Double, String> empFormatter =
(name, salary) -> "Employee: " + name + " | Salary: Rs." + salary;
System.out.println(empFormatter.apply("Sneha", 95000.0));
}
}
Lambda expression kevalam existing method ni direct ga invoke chesthunte, dhaanni inka compact ga rayadaniki Method Reference (::) vadathamu:
| Method Reference Type | Lambda Syntax | Method Reference Shorthand (::) |
|---|---|---|
| 1. Static Method | str -> Integer.parseInt(str) | Integer::parseInt |
| 2. Instance Method of Specific Object | x -> System.out.println(x) | System.out::println |
| 3. Instance Method of Arbitrary Object | str -> str.toUpperCase() | String::toUpperCase |
| 4. Constructor Reference | () -> new ArrayList<>() | ArrayList::new |
import java.util.List;
import java.util.ArrayList;
import java.util.function.Function;
import java.util.function.Supplier;
public class MethodRefDemo {
public static void main(String[] args) {
List<String> cities = List.of("hyderabad", "bengaluru", "chennai");
// 1. Instance Method Reference (String::toUpperCase)
cities.stream().map(String::toUpperCase).forEach(System.out::println);
// 2. Static Method Reference (Integer::parseInt)
Function<String, Integer> parser = Integer::parseInt;
System.out.println("Parsed: " + (parser.apply("500") + 100)); // 600
// 3. Constructor Reference (ArrayList::new)
Supplier<List<String>> listFactory = ArrayList::new;
List<String> dynamicList = listFactory.get();
dynamicList.add("Spring Boot");
System.out.println("Created List: " + dynamicList);
}
}
Java 8+ Collections lo forEach, removeIf, replaceAll, mariyu sort methods Lambdas tho integration ayyi unnay:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class Developer {
private String name;
private int experienceYears;
private double salary;
public Developer(String name, int exp, double salary) {
this.name = name;
this.experienceYears = exp;
this.salary = salary;
}
public String getName() { return name; }
public int getExperienceYears() { return experienceYears; }
public double getSalary() { return salary; }
@Override
public String toString() {
return name + " (" + experienceYears + " yrs) - Rs." + salary;
}
}
public class SortingDemo {
public static void main(String[] args) {
List<Developer> team = new ArrayList<>();
team.add(new Developer("Ravi", 4, 75000));
team.add(new Developer("Anu", 7, 120000));
team.add(new Developer("Kiran", 2, 45000));
team.add(new Developer("Bhavna", 7, 135000));
// 1. removeIf with Predicate: Remove developers with < 3 yrs experience
team.removeIf(dev -> dev.getExperienceYears() < 3);
// 2. Multi-Level Sorting: By Experience DESC, then by Salary DESC
team.sort(
Comparator.comparingInt(Developer::getExperienceYears).reversed()
.thenComparingDouble(Developer::getSalary).reversed()
);
System.out.println("=== Sorted Senior Developers ===");
team.forEach(System.out::println);
}
}
โ ๏ธ Critical Rule: Why Captured Variables Must Be Final
Lambda expression lopala outer method lo unna local variables ni read cheyyavachu (called Variable Capture). Kaani aa variable <code>final</code> or <code>effectively final</code> (declare chesina tharvatha reassign cheyyakunda undali)!
Why? Local variable Stack Memory lo untundhi. Outer method execute aypoyaka stack frame destroy avthundhi, kaani Lambda Object Heap lo untundhi. So Java local variable copy ni capture chesthundhi. Synchronization conflicts avoid cheyyadaniki Java reassignments ni prohibit chesthundhi!
public class VariableCaptureDemo {
public static void main(String[] args) {
String companyPrefix = "OUR_COMPILER_"; // Effectively final variable
List<String> roles = List.of("DEV", "TESTER", "ARCHITECT");
// โ
Allowed: Reading effectively final variable
roles.forEach(role -> {
System.out.println(companyPrefix + role);
});
// โ If you try to reassign:
// companyPrefix = "NEW_PREFIX_";
// Compilation Error: Variable used in lambda expression should be final or effectively final!
}
}
Run this complete functional lambda pipeline in our online Java compiler:
import java.util.List;
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
List<String> languages = List.of("Java", "JavaScript", "Python", "C++", "Julia");
Predicate<String> startsWithJ = lang -> lang.startsWith("J");
languages.stream()
.filter(startsWithJ)
.map(String::toUpperCase)
.forEach(System.out::println);
}
}