Introduction
Lambda expressions (Java 8+) provide a concise way to write anonymous functions. Enable functional programming in Java.
Basic Syntax
(parameters) -> expression
// Or with block
(parameters) -> {
statements;
return value;
}
Simple Examples
// No parameters
() -> System.out.println("Hello");
// One parameter (parentheses optional)
x -> x * x;
(x) -> x * x; // Same as above
// Multiple parameters
(x, y) -> x + y;
// With block
(x, y) -> {
int sum = x + y;
return sum;
};
Before Lambda (Anonymous Inner Class)
// Old way - anonymous inner class
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
r.run();
After Lambda (Concise)
// New way - lambda expression
Runnable r = () -> System.out.println("Hello World");
r.run();
Functional Interface
Interface with exactly one abstract method.
@FunctionalInterface
interface Calculator {
int calculate(int a, int b); // Single abstract method
}
public class Main {
public static void main(String[] args) {
// Lambda for addition
Calculator add = (a, b) -> a + b;
System.out.println(add.calculate(10, 5)); // 15
// Lambda for multiplication
Calculator multiply = (a, b) -> a * b;
System.out.println(multiply.calculate(10, 5)); // 50
// Lambda with block
Calculator subtract = (a, b) -> {
int result = a - b;
return result;
};
System.out.println(subtract.calculate(10, 5)); // 5
}
}
Lambda with Collections
forEach():
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Alice", "Bob");
// Lambda with forEach
names.forEach(name -> System.out.println(name));
// Or method reference
names.forEach(System.out::println);
}
}
Comparator:
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
// Lambda for sorting
Collections.sort(numbers, (a, b) -> a - b);
System.out.println(numbers); // [1, 2, 5, 8, 9]
// Descending
Collections.sort(numbers, (a, b) -> b - a);
System.out.println(numbers); // [9, 8, 5, 2, 1]
}
}
Lambda with Threads
public class Main {
public static void main(String[] args) {
// Old way
Thread t1 = new Thread(new Runnable() {
public void run() {
System.out.println("Thread 1 running");
}
});
// Lambda way
Thread t2 = new Thread(() -> System.out.println("Thread 2 running"));
t1.start();
t2.start();
}
}
Built-in Functional Interfaces
1. Predicate:
Test a condition (returns boolean)
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
// Check if number is even
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(5)); // false
// Check if string is long
Predicate<String> isLong = s -> s.length() > 5;
System.out.println(isLong.test("Hello")); // false
System.out.println(isLong.test("HelloWorld")); // true
}
}
2. Function<T, R>:
Transform input to output
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
// Convert String to Integer
Function<String, Integer> stringLength = s -> s.length();
System.out.println(stringLength.apply("Hello")); // 5
// Square a number
Function<Integer, Integer> square = n -> n * n;
System.out.println(square.apply(5)); // 25
}
}
3. Consumer:
Consume (use) input, no return
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
// Print with prefix
Consumer<String> print = s -> System.out.println("Value: " + s);
print.accept("Hello"); // Value: Hello
print.accept("World"); // Value: World
}
}
4. Supplier:
Supply (provide) output, no input
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
// Supply random number
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
System.out.println(randomValue.get());
}
}
Stream API with Lambda
filter():
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter even numbers
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evens); // [2, 4, 6, 8, 10]
}
}
map():
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Square each number
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squares); // [1, 4, 9, 16, 25]
}
}
reduce():
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Sum all numbers
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(sum); // 15
}
}
Complete Example: Student Processing
import java.util.*;
import java.util.stream.*;
class Student {
String name;
int marks;
Student(String name, int marks) {
this.name = name;
this.marks = marks;
}
@Override
public String toString() {
return name + " (" + marks + ")";
}
}
public class Main {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("John", 85),
new Student("Alice", 92),
new Student("Bob", 78),
new Student("Carol", 88),
new Student("David", 65)
);
// Filter students with marks > 80
System.out.println("Students with marks > 80:");
students.stream()
.filter(s -> s.marks > 80)
.forEach(System.out::println);
// Get names of top students
System.out.println("\nTop student names:");
List<String> topNames = students.stream()
.filter(s -> s.marks > 80)
.map(s -> s.name)
.collect(Collectors.toList());
System.out.println(topNames);
// Count students with marks > 80
long count = students.stream()
.filter(s -> s.marks > 80)
.count();
System.out.println("\nCount: " + count);
// Average marks
double average = students.stream()
.mapToInt(s -> s.marks)
.average()
.orElse(0.0);
System.out.println("Average: " + average);
// Sort by marks (descending)
System.out.println("\nSorted by marks (desc):");
students.stream()
.sorted((s1, s2) -> s2.marks - s1.marks)
.forEach(System.out::println);
}
}
Method References
Shorthand for lambda expressions.
Types:
- Static method:
ClassName::staticMethod - Instance method:
object::instanceMethod - Constructor:
ClassName::new - Instance method of type:
ClassName::instanceMethod
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Alice", "Bob");
// Lambda
names.forEach(name -> System.out.println(name));
// Method reference (equivalent)
names.forEach(System.out::println);
// Constructor reference
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<String> strings = numbers.stream()
.map(String::valueOf) // Integer to String
.collect(Collectors.toList());
System.out.println(strings); // [1, 2, 3]
}
}
Lambda Variable Capture
Lambda can access variables from enclosing scope (must be final or effectively final).
public class Main {
public static void main(String[] args) {
int multiplier = 10; // Effectively final
// Lambda captures multiplier
Function<Integer, Integer> multiply = n -> n * multiplier;
System.out.println(multiply.apply(5)); // 50
// multiplier = 20; // Error: cannot modify captured variable
}
}
Real-World Example: Calculator
import java.util.*;
import java.util.function.BiFunction;
public class Calculator {
// Store operations as lambdas
private Map<String, BiFunction<Integer, Integer, Integer>> operations;
public Calculator() {
operations = new HashMap<>();
// Register operations using lambdas
operations.put("add", (a, b) -> a + b);
operations.put("subtract", (a, b) -> a - b);
operations.put("multiply", (a, b) -> a * b);
operations.put("divide", (a, b) -> a / b);
operations.put("power", (a, b) -> (int) Math.pow(a, b));
operations.put("mod", (a, b) -> a % b);
}
public int calculate(String operation, int a, int b) {
BiFunction<Integer, Integer, Integer> op = operations.get(operation);
if (op == null) {
throw new IllegalArgumentException("Unknown operation: " + operation);
}
return op.apply(a, b);
}
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println("10 + 5 = " + calc.calculate("add", 10, 5));
System.out.println("10 - 5 = " + calc.calculate("subtract", 10, 5));
System.out.println("10 * 5 = " + calc.calculate("multiply", 10, 5));
System.out.println("10 / 5 = " + calc.calculate("divide", 10, 5));
System.out.println("2 ^ 5 = " + calc.calculate("power", 2, 5));
System.out.println("10 % 3 = " + calc.calculate("mod", 10, 3));
}
}
Lambda vs Anonymous Class
| Feature | Lambda | Anonymous Class |
|---|---|---|
| Syntax | Concise | Verbose |
| this | Enclosing class | Anonymous class |
| Variables | Effectively final | No restriction |
| Functional interface | Yes | Any interface |
| Performance | Better | Slower |
| Scope | Enclosing scope | Own scope |
Common Functional Interfaces
| Interface | Method | Use |
|---|---|---|
| Predicate | boolean test(T t) | Test condition |
| Function<T,R> | R apply(T t) | Transform |
| Consumer | void accept(T t) | Consume |
| Supplier | T get() | Supply |
| BiFunction<T,U,R> | R apply(T t, U u) | Two inputs |
| UnaryOperator | T apply(T t) | Same type I/O |
| BinaryOperator | T apply(T t1, T t2) | Two same type |
Quick Reference
// Basic syntax
(parameters) -> expression
(parameters) -> { statements; }
// No parameters
() -> System.out.println("Hello");
// One parameter
x -> x * x;
// Multiple parameters
(x, y) -> x + y;
// With type
(Integer x, Integer y) -> x + y;
// Functional interface
@FunctionalInterface
interface MyInterface {
void method();
}
MyInterface lambda = () -> System.out.println("Hello");
// Built-in interfaces
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> length = s -> s.length();
Consumer<String> print = s -> System.out.println(s);
Supplier<Double> random = () -> Math.random();
// Method reference
names.forEach(System.out::println);
Exam Tips
Remember:
- Lambda = anonymous function
- Syntax: (params) -> expression
- Functional interface required
- One abstract method only
- @FunctionalInterface annotation
- Predicate for testing
- Function for transformation
- Consumer for consuming
- Supplier for supplying
- Method reference shorthand
Common Questions:
- What are lambda expressions?
- Lambda syntax?
- What is functional interface?
- Built-in functional interfaces?
- Lambda vs anonymous class?
- Method reference types?
- Variable capture rules?
- Lambda with Streams?
- Benefits of lambda?
- When introduced in Java?