Courses/Java/Lambda Expressions

    Lesson 22 • Advanced

    Lambda Expressions Deep Dive

    Functional interfaces, method references, closures, and lambda best practices.

    Before You Start

    You should understand Interfaces (Lesson 11), Anonymous Classes (Lesson 19), and Streams API (Lesson 21). Lambdas are the building blocks of functional Java.

    What You'll Learn

    • ✅ Lambda syntax and type inference
    • ✅ Built-in functional interfaces: Predicate, Function, Consumer, Supplier
    • ✅ Method references (::) — four types
    • ✅ Composing functions and predicates
    • ✅ Effectively final variables and closures
    • ✅ Lambda vs anonymous class differences

    1️⃣ Lambdas: Functions as First-Class Citizens

    Before Java 8, passing behavior required verbose anonymous inner classes. Lambdas changed everything — they let you write (x, y) → x + y instead of a 5-line anonymous class. Under the hood, lambdas aren't syntactic sugar for anonymous classes; they use invokedynamic and are more efficient.

    💡 Analogy: Sticky Notes

    A lambda is like a sticky note with instructions. Instead of writing a formal letter (anonymous class) every time you want to tell someone what to do, you scribble a quick note: "sort by price" or "filter active users." The recipient (the method accepting the lambda) follows the note.

    Try It: Lambda Syntax & Functional Interfaces

    Try it Yourself »
    JavaScript
    // Lambda Expressions — Syntax & Functional Interfaces
    console.log("=== Lambda Syntax ===\n");
    
    // 1. Lambda syntax variations
    console.log("1. LAMBDA SYNTAX:");
    let greet = () => console.log("  Hello!");
    let square = x => x * x;
    let add = (a, b) => a + b;
    let process = (x) => { let result = x * 2; return result + 1; };
    
    greet();
    console.log("  square(5) =", square(5));
    console.log("  add(3, 4) =", add(3, 4));
    console.log("  process(5) =", process(5));
    
    // 2. Functional Interfaces (simulated)
    con
    ...

    2️⃣ Method References & Composition

    Method references (::) are shorthand for lambdas that just call an existing method. There are four types: static (Math::abs), instance on object (str::length), instance on class (String::toUpperCase), and constructor (ArrayList::new). Use them whenever a lambda simply delegates to an existing method.

    3️⃣ Composing Functions

    Java's functional interfaces support composition. Function.compose() and Function.andThen() chain transformations. Predicate.and(), .or(), and .negate() combine boolean tests. This creates readable, reusable logic from small building blocks.

    Try It: Method References & Composition

    Try it Yourself »
    JavaScript
    // Method References & Function Composition
    console.log("=== Method References & Composition ===\n");
    
    // 1. Method references (simulated)
    console.log("1. METHOD REFERENCES:");
    let words = ["Hello", "World", "Java"];
    let upper = words.map(s => s.toUpperCase());
    console.log("  String::toUpperCase →", upper.join(", "));
    
    // Constructor reference
    class Point {
        constructor(x, y) { this.x = x; this.y = y; }
        toString() { return "(" + this.x + "," + this.y + ")"; }
    }
    let coords = [[1,2], [3,4],
    ...

    4️⃣ Closures & Effectively Final

    Lambdas can capture local variables, but those variables must be effectively final — assigned exactly once. This restriction exists because lambdas might execute on a different thread than where the variable was declared. If you need a mutable counter inside a lambda, use AtomicInteger or an array wrapper.

    Try It: Closures & Pipeline Builder

    Try it Yourself »
    JavaScript
    // Closures & Pipeline Builder Pattern
    console.log("=== Closures & Pipelines ===\n");
    
    // 1. Closures
    console.log("1. CLOSURES (Effectively Final):");
    function createCounter(start) {
        let count = start;
        return {
            increment: () => ++count,
            get: () => count,
            reset: () => { count = start; }
        };
    }
    
    let counter = createCounter(10);
    console.log("  Start:", counter.get());
    counter.increment();
    counter.increment();
    console.log("  After 2 increments:", counter.get());
    cou
    ...

    Common Mistakes

    Modifying captured variables: int count = 0; list.forEach(x → count++); won't compile. Use AtomicInteger or int[] count = {0}.
    Long lambdas: If your lambda exceeds 3 lines, extract it to a named method. Lambdas should be concise and readable.
    Using lambda when method reference works: x → x.toString() should be Object::toString. Method references are more readable and slightly faster.

    Pro Tips

    💡 Predicate chaining is powerful: isAdult.and(isActive).negate() reads like English and is fully composable.

    💡 Function.compose() vs andThen(): f.compose(g) applies g first, then f. f.andThen(g) applies f first, then g.

    💡 Lambdas vs anonymous classes: this inside a lambda refers to the enclosing class, not the lambda itself.

    📋 Quick Reference

    InterfaceMethodSignature
    Predicate<T>test()T → boolean
    Function<T,R>apply()T → R
    Consumer<T>accept()T → void
    Supplier<T>get()() → T
    BiFunction<T,U,R>apply()(T, U) → R

    🎉 Lesson Complete!

    You've mastered lambda expressions, functional interfaces, and function composition!

    Next: Working with Optionals — eliminate null pointer exceptions with Optional chains.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service