Lambdas in Java are anonymous functions that allow you to write more concise and functional-style code. They were introduced in Java 8 as a way to implement functional interfaces more elegantly.
A lambda expression is essentially a short block of code that takes parameters and returns a value. It’s similar to methods, but doesn’t need a name and can be implemented as an assignment to a Functional Interface type.
Syntax
Lambda Syntax Structure The basic syntax of a lambda expression follows this pattern:
Arrow operator (->): Separates parameters from body
Body: Either a single expression or a block of statements
Syntax Examples
// No parameters() -> System.out.println("Hello World")// Single parameter (parentheses optional)x -> x * 2(x) -> x * 2 // equivalent// Multiple parameters(x, y) -> x + y// Block body(x, y) -> { int sum = x + y; return sum * 2;}// Type inference(String s) -> s.length() // explicit types -> s.length() // inferred type
Declaration
Lambdas can only be used with Functional Interfaces. Common examples include:
Runnable
Comparator<T>
Predicate<T>
Function<T, R>
Consumer<T>
Supplier<T>
// Using built-in functional interfacesRunnable task = () -> System.out.println("Running task");Predicate<String> isEmpty = s -> s.isEmpty();Function<String, Integer> stringLength = s -> s.length();Comparator<String> lengthComparator = (s1, s2) -> Integer.compare(s1.length(), s2.length());// Custom functional interface@FunctionalInterfaceinterface Calculator { int calculate(int a, int b);}Calculator add = (a, b) -> a + b;Calculator multiply = (a, b) -> a * b;
Variable Capture
Lambdas can only capture variables that are final or effectively final(variables that are not reassigned after initialisation).
public void demonstrateCapture() { int multiplier = 10; // effectively final String prefix = "Result: "; // effectively final Function<Integer, String> formatter = x -> prefix + (x * multiplier); // This would cause a compilation error: // multiplier = 20; // Cannot modify captured variable}
How Variable Capture Works (Conceptual Unwrapping)
Lambda:
int multiplier = 10;Function<Integer, Integer> doubler = x -> x * multiplier;
The JVM at runtime generates a synthetic class:
// Generated synthetic class (conceptual representation)final class LambdaClass$1 implements Function<Integer, String> { private final int arg$1; // field for captured multiplier // Special constructor for passing in captured variables LambdaClass$1(int multiplier) { this.arg$1 = multiplier; } @Override public String apply(Integer x) { return x * arg$1; }}
LambdaMetafactory and MethodHandle are then used to initialise this synthetic class and assign it to doubler
Currying
Function<Integer, Function<Integer, Integer>> represents currying.
Transforming a function that takes multiple arguments into a sequence of functions that each take a single argument.
// Traditional two-parameter function implemented with currying// The first Function returns a second Function that adds the first Function's agument to the second function's argumentFunction<Integer, Function<Integer, Integer>> add = x -> y -> x + y;// UsageFunction<Integer, Integer> add5 = add.apply(5);Integer result = add5.apply(3); // result = 8// Or in one lineInteger directResult = add.apply(5).apply(3); // result = 8
Three level currying
// Three-level nestingFunction<Integer, Function<Integer, Function<Integer, Integer>>> threeLevelAdd = x -> y -> z -> x + y + z;Integer sum = threeLevelAdd.apply(1).apply(2).apply(3); // sum = 6
Key Benefits of Lambdas
Conciseness: Less boilerplate code compared to anonymous classes
Readability: More expressive and functional programming style
Type inference: Compiler can often infer parameter types
Closure support: Can capture variables from enclosing scope