Skip to content
intermediatePhase 15 · Java 8+ Features

Lambda Expressions

Write concise anonymous functions with lambda expressions.

45m
2 problems
Topic Progress0%

Lambda Syntax

Lambda Syntax

Lambda expressions provide a concise way to implement functional interfaces (single abstract method interfaces). They were introduced in Java 8.

Basic syntax:

(parameters) -> expression
(parameters) -> { statements; }
import java.util.*;

public class LambdaSyntaxDemo {
    public static void main(String[] args) {
        // No parameters
        Runnable r = () -> System.out.println("Hello from lambda");
        r.run();

        // Single parameter (parentheses optional)
        java.util.function.Consumer<String> printer = s -> System.out.println(s);
        printer.accept("Print me");

        // Multiple parameters
        java.util.function.BiPredicate<String, String> startsWith =
            (s, prefix) -> s.startsWith(prefix);
        System.out.println(startsWith.test("Hello", "He")); // true

        // Single expression (return implicit)
        java.util.function.Function<String, Integer> length = s -> s.length();
        System.out.println(length.apply("Hello")); // 5

        // Block body (explicit return needed)
        java.util.function.Function<String, String> transform = s -> {
            String trimmed = s.trim();
            return trimmed.toUpperCase();
        };
        System.out.println(transform.apply("  hello  ")); // HELLO

        // Multiple lines in block
        java.util.function.Function<List<Integer>, Integer> sum = list -> {
            int total = 0;
            for (int num : list) {
                total += num;
            };
            return total;
        };
        System.out.println(sum.apply(Arrays.asList(1, 2, 3, 4))); // 10
    }
}

Syntax variations:

  • () -> expr — no params
  • x -> expr — one param, no parens
  • (x, y) -> expr — multiple params
  • (x) -> { return x * 2; } — block body with return
  • x -> x * 2 — expression body (implicit return)

vs Anonymous Classes

Lambda vs Anonymous Classes

Lambdas are a more concise alternative to anonymous inner classes for implementing functional interfaces.

import java.util.*;

public class LambdaVsAnonymousDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");

        // Anonymous class - verbose
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.compareTo(b);
            }
        });
        System.out.println("Anonymous: " + names);

        // Lambda - concise
        names.sort((a, b) -> a.compareTo(b));
        System.out.println("Lambda: " + names);

        // Even more concise with method reference
        names.sort(String::compareTo);
        System.out.println("Method ref: " + names);

        // Key differences:
        // 1. Lambdas are for functional interfaces (SAM)
        // 2. Anonymous classes can implement any interface or extend any class
        // 3. Lambdas have simplified syntax
        // 4. 'this' in lambda refers to the enclosing class
        // 5. 'this' in anonymous class refers to the anonymous class

        // Lambda with multiple statements
        names.sort((a, b) -> {
            int lengthCompare = Integer.compare(a.length(), b.length());
            if (lengthCompare != 0) return lengthCompare;
            return a.compareTo(b);
        });
        System.out.println("Custom sort: " + names);

        // Functional interfaces for common patterns
        java.util.function.Predicate<String> isLong = s -> s.length() > 3;
        java.util.function.Function<String, Integer> toLength = String::length;
        java.util.function.Consumer<String> shout = s -> System.out.println(s.toUpperCase() + "!");
        java.util.function.Supplier<String> greet = () -> "Hello!";

        System.out.println("Long? " + isLong.test("Hello")); // true
        System.out.println("Length: " + toLength.apply("Hi")); // 2
        shout.accept("wow"); // WOW!
        System.out.println("Greeting: " + greet.get()); // Hello!
    }
}

Differences:

Aspect Lambda Anonymous Class
Interface Functional only Any interface/class
Syntax Concise Verbose
'this' Enclosing class Anonymous class
Scope Effectively final vars Can access local vars
Performance May be invoked directly Creates a new class

Effective Final

Effectively Final Variables

Lambdas can only capture local variables that are final or effectively final. A variable is effectively final if it is assigned once and never modified after.

import java.util.*;
import java.util.function.*;

public class EffectiveFinalDemo {
    public static void main(String[] args) {
        // Effectively final - no explicit final needed
        String greeting = "Hello"; // effectively final
        Consumer<String> printer = s -> System.out.println(greeting + ", " + s);
        printer.accept("World"); // Hello, World

        // This would NOT compile:
        // int count = 0;
        // Consumer<String> inc = s -> count++; // COMPILE ERROR: not final

        // Workaround: use array or AtomicInteger
        int[] count = {0}; // array reference is final
        Consumer<String> inc = s -> count[0]++; // OK - modifying array element
        inc.accept("test");
        System.out.println("Count: " + count[0]); // 1

        // Method parameters are effectively final
        processWithCallback("data", result -> {
            System.out.println("Processed: " + result);
        });

        // Capturing loop variables - workaround with index
        List<Runnable> runners = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            final int index = i; // explicitly final
            runners.add(() -> System.out.println("Task " + index));
        }
        runners.forEach(Runnable::run); // 0 1 2 3 4

        // Java 9+ effectively final in for-each
        String[] names = {"Alice", "Bob", "Charlie"};
        List<Supplier<String>> suppliers = new ArrayList<>();
        for (String name : names) { // name is effectively final in each iteration
            suppliers.add(() -> name);
        }
        suppliers.forEach(s -> System.out.println(s.get()));
    }

    static void processWithCallback(String data, Consumer<String> callback) {
        String processed = data.toUpperCase(); // effectively final
        callback.accept(processed);
    }
}

Key rules:

  • A variable captured by a lambda must be final or effectively final
  • "Effectively final" means the variable is assigned once and never modified
  • Use arrays, AtomicInteger, or wrapper objects for mutable state
  • Loop variables with index require final or effectively final workarounds

Use Cases

Lambda Use Cases

Lambdas are used extensively with functional interfaces for callbacks, streams, and functional operations.

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class LambdaUseCasesDemo {
    public static void main(String[] args) {
        // 1. Event handling / Callbacks
        Button button = new Button();
        button.setOnClickListener(e -> System.out.println("Button clicked!"));
        button.click();

        // 2. Sorting
        List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
        names.sort((a, b) -> a.compareTo(b));
        names.sort(Comparator.naturalOrder());
        System.out.println("Sorted: " + names);

        // 3. Filtering
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List<Integer> evens = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());
        System.out.println("Evens: " + evens);

        // 4. Mapping
        List<String> upper = names.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        System.out.println("Upper: " + upper);

        // 5. Custom thread execution
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("Thread: " + i);
            }
        });
        t.start();

        // 6. Optional handling
        Optional.ofNullable("hello")
            .filter(s -> s.length() > 3)
            .ifPresent(s -> System.out.println("Long string: " + s));

        // 7. Predicate chaining
        Predicate<String> startsWithA = s -> s.startsWith("A");
        Predicate<String> hasLength3 = s -> s.length() == 3;
        Predicate<String> combined = startsWithA.and(hasLength3);
        System.out.println("'ABC' matches: " + combined.test("ABC")); // true
        System.out.println("'ABCD' matches: " + combined.test("ABCD")); // false

        // 8. Function chaining
        Function<String, String> trim = String::trim;
        Function<String, String> lower = String::toLowerCase;
        Function<String, String> process = trim.andThen(lower);
        System.out.println("Processed: " + process.apply("  HELLO  ")); // hello

        // 9. Reducing
        int sum = numbers.stream()
            .reduce(0, (a, b) -> a + b);
        System.out.println("Sum: " + sum);

        // 10. Creating collections with lambdas
        Map<String, Integer> wordMap = Map.of("a", 1, "b", 2, "c", 3);
        wordMap.forEach((key, value) ->
            System.out.println(key + " = " + value));
    }

    // Simple Button class for demo
    static class Button {
        private Consumer<Object> listener;
        public void setOnClickListener(Consumer<Object> listener) {
            this.listener = listener;
        }
        public void click() {
            if (listener != null) listener.accept(null);
        }
    }
}

Top use cases:

  1. Callbacks and event handlers
  2. Sorting with Comparator
  3. Stream operations (filter, map, reduce)
  4. Thread/Runnable creation
  5. Predicate/Function chaining
  6. Optional.ifPresent()

Practice Problems

0/2solved
Sort with Custom Lambda

Given a list of strings, sort them by length (shortest first) using a lambda expression. Then sort them alphabetically using a method reference.

Solution
import java.util.*;

public class LambdaSorter {
    public static List<String> sortByLength(List<String> strings) {
        List<String> result = new ArrayList<>(strings);
        result.sort((a, b) -> a.length() - b.length());
        return result;
    }

    public static List<String> sortByNatural(List<String> strings) {
        List<String> result = new ArrayList<>(strings);
        result.sort(String::compareTo);
        return result;
    }
}
Lambda Callback Pattern

Create a method `executeWithRetry(int maxRetries, Runnable action, Consumer<String> logger)` that executes the action. If it throws an exception, log the error and retry up to maxRetries times.

Solution
public class RetryExecutor {
    public static void executeWithRetry(int maxRetries, Runnable action, Consumer<String> logger) {
        for (int i = 0; i <= maxRetries; i++) {
            try {
                action.run();
                logger.accept("Attempt " + (i + 1) + " succeeded");
                return;
            } catch (Exception e) {
                logger.accept("Attempt " + (i + 1) + " failed: " + e.getMessage());
            }
        }
        logger.accept("All " + (maxRetries + 1) + " attempts failed");
    }
}

Quiz

1. What is a lambda expression in Java?

Question 1 options

2. What does 'effectively final' mean for lambda variables?

Question 2 options

3. Which of these is NOT a valid lambda expression?

Question 3 options

4. What is the primary purpose of Java Lambdas?

Question 4 options

Flashcards

Question

What is the syntax of a lambda expression?

Answer

(parameters) -> expression for single expression, or (parameters) -> { statements; } for block body. Single parameter can omit parentheses: x -> x * 2.

Question

What is the difference between lambda and anonymous class?

Answer

Lambdas are for functional interfaces only, more concise, 'this' refers to enclosing class, and variables must be effectively final. Anonymous classes can implement any interface or extend classes.

Question

What is effectively final in the context of lambdas?

Answer

A local variable that is assigned once and never modified after initialization. Lambdas can capture effectively final variables. Use arrays or AtomicInteger for mutable state.

Question

What is Java Lambdas?

Answer

Java Lambdas is a key concept in Java programming.

Question

When to use Java Lambdas?

Answer

Use Java Lambdas when building production systems that require reliability, scalability, and maintainability.

Revision Notes

Key Takeaways

  • 1.Lambdas provide concise syntax for functional interfaces
  • 2.Variables captured must be final or effectively final
  • 3.'this' in lambda refers to the enclosing class, not the lambda
  • 4.Lambdas enable clean stream operations, callbacks, and sorting

Interview Tips

  • Explain lambda syntax and how it differs from anonymous classes
  • Discuss effectively final and why lambdas capture variables this way
  • Demonstrate lambdas with streams, sorting, and callbacks
  • Know the common functional interfaces: Predicate, Function, Consumer, Supplier

Cheat Sheet

Lambda Expressions Cheat Sheet

Syntax

  • No params: () -> expr
  • One param: x -> expr
  • Multiple params: (x, y) -> expr
  • Block body: (x) -> { return x * 2; }

vs Anonymous Classes

  • Functional interfaces only
  • More concise
  • 'this' = enclosing class
  • Variables must be effectively final

Effectively Final

  • Assigned once, never modified
  • Lambdas capture these variables
  • Use arrays/AtomicInteger for mutability

Common Use Cases

  • Callbacks and event handlers
  • Sorting with Comparator
  • Stream operations
  • Thread creation
  • Predicate/Function chaining