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!
// Traditional way: Create a whole interface and classinterface 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:
// 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 returnBefore and After Lambda
Let's see how lambda expressions make code much shorter and cleaner:
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:
// Zero parametersRunnable 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 typeConsumer<String> typedPrinter = (String message) -> System.out.println(message);// Two parametersBiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b;System.out.println(adder.apply(5, 3)); // Output: 8// Two parameters with typesBiFunction<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 handlerButton 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):
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!)@FunctionalInterfaceinterface 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) -> TReal-World Examples
Let's see how lambda expressions are used in everyday programming:
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 demonstrationclass 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