Lambda Expressions in Java

Learn how to write short, clean code with lambda expressions

Think of lambda expressions like shortcuts! Instead of writing a long recipe every time you want to make a sandwich, you just say 'make sandwich' and everyone knows what to do. Lambda expressions let you write code in a super short way - instead of writing a whole method, you write just one line!

What is a Lambda Expression?

A lambda expression is a short way to write a method that you'll only use once. It's like writing an anonymous method (a method without a name). Instead of creating a whole class and method, you write the code directly where you need it!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Traditional way: Create a whole interface and class
interface Greeting {
void sayHello(String name);
}
class TraditionalGreeting implements Greeting {
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
}
// Using it:
Greeting greeting = new TraditionalGreeting();
greeting.sayHello("Alice"); // Output: Hello, Alice!
// Lambda way: Write it in ONE line!
Greeting lambdaGreeting = (name) -> System.out.println("Hello, " + name + "!");
lambdaGreeting.sayHello("Bob"); // Output: Hello, Bob!
// WOW! From 10 lines to 1 line! 🚀

Lambda Syntax

Lambda expressions have a simple syntax with three parts:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Lambda Syntax:
// (parameters) -> expression
// OR
// (parameters) -> { statements; }
// Part 1: Parameters (in parentheses)
// Part 2: Arrow operator ->
// Part 3: Body (expression or code block)
// Example 1: No parameters
() -> System.out.println("Hello!")
// Example 2: One parameter (parentheses optional)
name -> System.out.println("Hello, " + name)
// OR with parentheses:
(name) -> System.out.println("Hello, " + name)
// Example 3: Multiple parameters
(a, b) -> a + b
// Example 4: Multiple statements (need curly braces)
(a, b) -> {
int sum = a + b;
System.out.println("Sum is: " + sum);
return sum;
}
// Example 5: With type declarations (optional)
(int a, int b) -> a + b
// Example 6: Single expression with return
(x) -> x * x // Returns x squared
// Remember: If you have only one statement, you don't need curly braces!
// If you have multiple statements, use { } and explicit return

Before and After Lambda

Let's see how lambda expressions make code much shorter and cleaner:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.*;
public class BeforeAfterLambda {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// ============ BEFORE JAVA 8 (OLD WAY) ============
// Sorting with anonymous class (SO MUCH CODE!)
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// 7 lines of code! 😫
// ============ AFTER JAVA 8 (WITH LAMBDA) ============
// Sorting with lambda (ONE LINE!)
Collections.sort(names, (a, b) -> a.compareTo(b));
// 1 line of code! 🎉
// Even shorter with method reference:
Collections.sort(names, String::compareTo);
// ============ ANOTHER EXAMPLE ============
// OLD WAY: Print each name
for (String name : names) {
System.out.println(name);
}
// 3 lines
// NEW WAY: With lambda
names.forEach(name -> System.out.println(name));
// 1 line!
// Even shorter:
names.forEach(System.out::println);
// ============ FILTERING EXAMPLE ============
// OLD WAY: Find names starting with 'A'
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("A")) {
result.add(name);
}
}
// 5 lines
// NEW WAY: With lambda and streams
List<String> lambdaResult = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
// 3 lines, much cleaner!
System.out.println("Names starting with A: " + lambdaResult);
}
}

Lambda Parameters

Lambda expressions can have zero, one, or multiple parameters:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Zero parameters
Runnable r = () -> System.out.println("Running!");
r.run(); // Output: Running!
// One parameter (parentheses optional)
Consumer<String> printer = message -> System.out.println(message);
printer.accept("Hello World"); // Output: Hello World
// One parameter with type
Consumer<String> typedPrinter = (String message) -> System.out.println(message);
// Two parameters
BiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b;
System.out.println(adder.apply(5, 3)); // Output: 8
// Two parameters with types
BiFunction<Integer, Integer, Integer> typedAdder =
(Integer a, Integer b) -> a + b;
// Multiple statements (need curly braces and return)
BiFunction<Integer, Integer, Integer> calculator = (a, b) -> {
int sum = a + b;
System.out.println("Calculating: " + a + " + " + b);
return sum;
};
System.out.println("Result: " + calculator.apply(10, 20));
// Output:
// Calculating: 10 + 20
// Result: 30
// Real-world example: Button click handler
Button button = new Button("Click Me");
button.setOnAction(event -> {
System.out.println("Button clicked!");
// Do something...
});
// Tip: Java can infer parameter types, so you usually don't need to write them!
// Only write types if you need to be explicit or if Java can't figure it out.

Lambda with Functional Interfaces

Lambda expressions work with functional interfaces (interfaces with only one method):

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import java.util.function.*;
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// 1. Predicate<T> - Takes input, returns boolean
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println("Is 4 even? " + isEven.test(4)); // true
System.out.println("Is 7 even? " + isEven.test(7)); // false
Predicate<String> isEmpty = str -> str.isEmpty();
System.out.println("Is '' empty? " + isEmpty.test("")); // true
// 2. Function<T, R> - Takes input T, returns output R
Function<String, Integer> stringLength = str -> str.length();
System.out.println("Length of 'Hello': " + stringLength.apply("Hello")); // 5
Function<Integer, Integer> square = num -> num * num;
System.out.println("Square of 5: " + square.apply(5)); // 25
// 3. Consumer<T> - Takes input, returns nothing (void)
Consumer<String> printer = message -> System.out.println("Message: " + message);
printer.accept("Hello!"); // Output: Message: Hello!
Consumer<Integer> multiplePrinter = num -> {
System.out.println(num + " x 2 = " + (num * 2));
System.out.println(num + " x 3 = " + (num * 3));
};
multiplePrinter.accept(5);
// Output:
// 5 x 2 = 10
// 5 x 3 = 15
// 4. Supplier<T> - Takes nothing, returns output T
Supplier<Double> randomNumber = () -> Math.random();
System.out.println("Random: " + randomNumber.get());
Supplier<String> greeting = () -> "Hello, World!";
System.out.println(greeting.get()); // Hello, World!
// 5. BiFunction<T, U, R> - Takes 2 inputs, returns output
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println("3 * 4 = " + multiply.apply(3, 4)); // 12
BiFunction<String, String, String> concat = (s1, s2) -> s1 + " " + s2;
System.out.println(concat.apply("Hello", "World")); // Hello World
// 6. UnaryOperator<T> - Takes T, returns T (special Function)
UnaryOperator<Integer> triple = num -> num * 3;
System.out.println("Triple of 7: " + triple.apply(7)); // 21
// 7. BinaryOperator<T> - Takes 2 T's, returns T (special BiFunction)
BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
System.out.println("Max of 10 and 20: " + max.apply(10, 20)); // 20
// Custom functional interface
Calculator calc = (a, b) -> a + b;
System.out.println("5 + 3 = " + calc.calculate(5, 3)); // 8
}
}
// Custom functional interface (only ONE abstract method!)
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
// Summary of common functional interfaces:
// Predicate<T> : T -> boolean
// Function<T,R> : T -> R
// Consumer<T> : T -> void
// Supplier<T> : () -> T
// BiFunction<T,U,R>: (T, U) -> R
// UnaryOperator<T> : T -> T
// BinaryOperator<T>: (T, T) -> T

Real-World Examples

Let's see how lambda expressions are used in everyday programming:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import java.util.*;
import java.util.stream.*;
public class RealWorldLambda {
public static void main(String[] args) {
// Example 1: Filtering and processing lists
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Get even numbers, double them, and collect
List<Integer> evenDoubled = numbers.stream()
.filter(n -> n % 2 == 0) // Keep only even
.map(n -> n * 2) // Double each
.collect(Collectors.toList());
System.out.println("Even doubled: " + evenDoubled); // [4, 8, 12, 16, 20]
// Example 2: Sorting with custom logic
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Sort by length
names.sort((a, b) -> a.length() - b.length());
System.out.println("By length: " + names); // [Bob, Alice, David, Charlie]
// Sort in reverse alphabetical order
names.sort((a, b) -> b.compareTo(a));
System.out.println("Reverse: " + names);
// Example 3: Working with employees
List<Employee> employees = Arrays.asList(
new Employee("Alice", 50000, 25),
new Employee("Bob", 60000, 30),
new Employee("Charlie", 55000, 28),
new Employee("David", 70000, 35)
);
// Find employees earning more than 55000
List<Employee> highEarners = employees.stream()
.filter(e -> e.salary > 55000)
.collect(Collectors.toList());
System.out.println("\nHigh earners:");
highEarners.forEach(e -> System.out.println(e.name + ": $" + e.salary));
// Get average salary
double avgSalary = employees.stream()
.mapToDouble(e -> e.salary)
.average()
.orElse(0);
System.out.println("\nAverage salary: $" + avgSalary);
// Example 4: Thread creation
// Old way:
Thread oldThread = new Thread(new Runnable() {
public void run() {
System.out.println("Old thread running");
}
});
// Lambda way:
Thread newThread = new Thread(() -> System.out.println("Lambda thread running"));
newThread.start();
// Example 5: Event handlers (JavaFX style)
Button button = new Button("Click Me");
button.setOnClick(() -> System.out.println("Button clicked!"));
// Example 6: Conditional operations
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
// Remove if condition is true
fruits.removeIf(fruit -> fruit.startsWith("b"));
System.out.println("\nFruits: " + fruits); // [apple, cherry, date]
// Example 7: Map operations
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 85);
scores.put("Bob", 92);
scores.put("Charlie", 78);
// Update all values
scores.replaceAll((name, score) -> score + 5); // Bonus points!
System.out.println("\nUpdated scores: " + scores);
// Print each entry
scores.forEach((name, score) ->
System.out.println(name + " scored " + score)
);
}
}
class Employee {
String name;
double salary;
int age;
Employee(String name, double salary, int age) {
this.name = name;
this.salary = salary;
this.age = age;
}
}
// Simple button class for demonstration
class Button {
String text;
Runnable onClick;
Button(String text) {
this.text = text;
}
void setOnClick(Runnable action) {
this.onClick = action;
}
}

Key Concepts

Anonymous Function

Lambda expressions are anonymous functions - they don't have a name. You use them once and forget about them!

Functional Interfaces

Lambda expressions only work with functional interfaces (interfaces with exactly one abstract method).

Type Inference

Java can automatically figure out the parameter types, so you don't always need to write them.

Cleaner Code

Lambda expressions make your code shorter, cleaner, and easier to read.

Best Practices

  • Keep lambda expressions short (1-3 lines max)
  • Use method references when possible for even cleaner code
  • Use descriptive parameter names even in short lambdas
  • Don't write complex logic in lambdas - create a separate method instead
  • Use lambda expressions with Stream API for powerful data processing

Common Mistakes

Writing long, complex lambda expressions

Why it's wrong: Lambda expressions should be short and simple. If it's complex, create a regular method!

Using lambda with non-functional interfaces

Why it's wrong: Lambda expressions only work with interfaces that have exactly one abstract method.

Modifying external variables inside lambda

Why it's wrong: Variables used in lambda must be 'effectively final' (never changed after initialization).

Not understanding what the lambda returns

Why it's wrong: Make sure you know what your lambda expression returns - it's easy to forget!

Interview Tips

  • 💡Explain that lambda expressions are a Java 8 feature
  • 💡Know the basic syntax: (parameters) -> expression
  • 💡Understand functional interfaces and how they relate to lambdas
  • 💡Be able to convert traditional code to lambda expressions
  • 💡Explain the benefits: cleaner code, less boilerplate, better readability
  • 💡Know common use cases: sorting, filtering, event handlers, callbacks