Java Lambda Expressions
Master the art of writing clean, functional code with Java's powerful lambda expressions introduced in Java 8.
What is a Lambda Expression?
A lambda expression is a short, anonymous function that can be passed around as a parameter. It provides a clear and concise way to represent a method interface using an expression. Think of it as a shortcut to write code without creating a full method or class.
Lambda expressions enable functional programming in Java, making your code more readable and maintainable.
- No need to write lengthy anonymous inner classes
- Makes code shorter and more readable
- Enables functional programming style in Java
- Works with functional interfaces (interfaces with single abstract method)
Fun Fact: Lambda expressions were introduced in Java 8 (2014) and revolutionized the way Java developers write code, making it more similar to modern languages like Python and JavaScript!
Lambda Expression Syntax
The basic syntax of a lambda expression consists of three parts:
Lambda Syntax Structure
(parameters) -> expression
// OR
(parameters) -> { statements; }
Components:
- Parameters: Input parameters (can be zero or more)
- Arrow Token (->): Separates parameters from body
- Body: Contains expressions or statements
Note: If there's only one parameter, you can omit parentheses. If there's only one statement, you can omit curly braces and the return keyword.
Before and After Lambda
Let's compare traditional approach vs lambda expression approach:
Example: Without Lambda (Traditional Way)
// Functional Interface
interface Greeting {
void sayHello(String name);
}
public class Main {
public static void main(String[] args) {
// Traditional approach using anonymous inner class
Greeting greeting = new Greeting() {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
};
greeting.sayHello("John");
}
}
Hello, John!
Example: With Lambda (Modern Way)
// Functional Interface
interface Greeting {
void sayHello(String name);
}
public class Main {
public static void main(String[] args) {
// Lambda expression - much cleaner!
Greeting greeting = (name) -> System.out.println("Hello, " + name + "!");
greeting.sayHello("John");
}
}
Hello, John!
Comparison:
- Traditional: 8 lines of code with boilerplate
- Lambda: 1 line of clean, readable code
- Same functionality: Both produce identical results
Types of Lambda Expressions
1. No Parameters
Lambda with no parameters uses empty parentheses.
Example: () -> System.out.println("Hello!");
2. Single Parameter
When there's only one parameter, parentheses are optional.
Example: name -> System.out.println(name); or (name) -> System.out.println(name);
3. Multiple Parameters
Multiple parameters must be enclosed in parentheses.
Example: (a, b) -> a + b;
4. With Return Statement
For multiple statements, use curly braces and explicit return.
Example: (a, b) -> { int sum = a + b; return sum; }
Lambda with Different Scenarios
Here are various examples demonstrating different lambda use cases:
Example: Lambda Variations
// 1. No parameters
interface Message {
void display();
}
// 2. Single parameter
interface Square {
int calculate(int x);
}
// 3. Multiple parameters
interface Sum {
int add(int a, int b);
}
public class Main {
public static void main(String[] args) {
// 1. Lambda with no parameters
Message msg = () -> System.out.println("Welcome to Lambda!");
msg.display();
// 2. Lambda with single parameter (no parentheses)
Square square = x -> x * x;
System.out.println("Square of 5: " + square.calculate(5));
// 3. Lambda with multiple parameters
Sum sum = (a, b) -> a + b;
System.out.println("Sum: " + sum.add(10, 20));
// 4. Lambda with multiple statements
Sum complex = (a, b) -> {
int result = a + b;
System.out.println("Calculating sum...");
return result;
};
System.out.println("Complex Sum: " + complex.add(15, 25));
}
}
Welcome to Lambda! Square of 5: 25 Sum: 30 Calculating sum... Complex Sum: 40
Explanation:
- No parameters: Uses empty parentheses () before arrow
- Single parameter: Parentheses optional, direct parameter name
- Multiple parameters: Comma-separated parameters in parentheses
- Multiple statements: Requires curly braces and explicit return
Lambda with Collections
Lambda expressions are extremely useful when working with Java Collections and Streams:
Example: Lambda with ArrayList
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
ArrayList fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Mango");
fruits.add("Banana");
fruits.add("Orange");
System.out.println("Original List: " + fruits);
// Lambda with forEach - Print each element
System.out.println("\nUsing forEach with Lambda:");
fruits.forEach(fruit -> System.out.println("- " + fruit));
// Lambda with sort
Collections.sort(fruits, (f1, f2) -> f1.compareTo(f2));
System.out.println("\nSorted List: " + fruits);
// Lambda with removeIf
fruits.removeIf(fruit -> fruit.startsWith("M"));
System.out.println("\nAfter removing fruits starting with 'M': " + fruits);
}
}
Original List: [Apple, Mango, Banana, Orange] Using forEach with Lambda: - Apple - Mango - Banana - Orange Sorted List: [Apple, Banana, Mango, Orange] After removing fruits starting with 'M': [Apple, Banana, Orange]
Explanation:
- forEach(): Iterates through each element using lambda
- sort(): Custom sorting logic using lambda comparator
- removeIf(): Removes elements based on lambda condition
- Concise code: All operations done in single lines
Lambda with Threads
Lambda expressions make creating threads much simpler:
Example: Thread Creation with Lambda
public class Main {
public static void main(String[] args) {
// Traditional way
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread 1: Traditional approach");
}
});
// Lambda way - Much cleaner!
Thread thread2 = new Thread(() -> {
System.out.println("Thread 2: Lambda approach");
});
// Even shorter lambda
Thread thread3 = new Thread(() -> System.out.println("Thread 3: One-line lambda"));
thread1.start();
thread2.start();
thread3.start();
}
}
Thread 1: Traditional approach Thread 2: Lambda approach Thread 3: One-line lambda
Explanation:
- Runnable interface: Has single abstract method run(), perfect for lambda
- Traditional vs Lambda: 6 lines reduced to 1 line
- Readability: Lambda makes thread creation much clearer
Built-in Functional Interfaces
Java provides several built-in functional interfaces in the java.util.function package:
Example: Using Predicate and Function
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
// Predicate: Takes input, returns boolean
Predicate isEven = num -> num % 2 == 0;
System.out.println("Is 10 even? " + isEven.test(10));
System.out.println("Is 7 even? " + isEven.test(7));
// Function: Takes input, returns output
Function stringLength = str -> str.length();
System.out.println("\nLength of 'Lambda': " + stringLength.apply("Lambda"));
// Consumer: Takes input, returns nothing
Consumer printUpperCase = str -> System.out.println(str.toUpperCase());
System.out.print("\nUpperCase: ");
printUpperCase.accept("hello world");
}
}
Is 10 even? true Is 7 even? false Length of 'Lambda': 6 UpperCase: HELLO WORLD
Common Functional Interfaces:
- Predicate<T>: Takes T, returns boolean (for conditions/filters)
- Function<T,R>: Takes T, returns R (for transformations)
- Consumer<T>: Takes T, returns nothing (for actions)
- Supplier<T>: Takes nothing, returns T (for generating values)
Advantages of Lambda Expressions
- Concise Code: Reduces boilerplate code significantly
- Readable: Makes code more clear and easier to understand
- Functional Programming: Enables functional programming paradigm in Java
- Better Performance: Can improve performance in some scenarios with parallel processing
- Easy Maintenance: Less code means fewer bugs and easier maintenance
- Stream API: Works seamlessly with Java 8 Stream API for data processing
💡 When to Use Lambda: Use lambda expressions when you need to pass simple behavior as a parameter, especially with functional interfaces. Avoid using them for complex logic that would be better suited in a named method.
Key Points to Remember
- Lambda expressions work only with functional interfaces (interfaces with single abstract method)
- Parentheses are optional for single parameter, mandatory for zero or multiple parameters
- Curly braces are optional for single expression, mandatory for multiple statements
- Return keyword is optional for single expression, mandatory with curly braces
- Lambda can access effectively final local variables from enclosing scope
- Use @FunctionalInterface annotation to mark functional interfaces (optional but recommended)
Best Practice: Keep lambda expressions short and simple. If your lambda becomes too complex (more than 3-4 lines), consider extracting it into a separate method for better readability.