Programming in Java

Unit 10: Nested Classes & Lambda Expressions

From verbose anonymous classes to elegant one-liners — master lambda expressions, functional interfaces, and method references to write modern, expressive Java.

⏱ 6 hrs theory + 4 hrs lab  |  💰 ₹10K–₹30K/month  |  📝 30 MCQs (Bloom's Mapped)

💼 Jobs this unlocks: Java Backend Developer (₹5–10 LPA)  |  Spring Boot Engineer (₹8–18 LPA)  |  Full-Stack Java Developer (₹6–14 LPA)

Section A

Opening Hook — One Line That Changed Java Forever

🏢 How Swiggy Calculates Your Delivery Fee in One Line of Java

Every time you order biryani on Swiggy, a delivery fee is calculated based on distance, restaurant load, surge pricing, and weather. Before Java 8, Swiggy's engineering team would have written something like this:

Java — Before (Anonymous Class)
interface FeeCalculator {
    double calculate(Order order);
}

FeeCalculator calculator = new FeeCalculator() {
    @Override
    public double calculate(Order order) {
        return order.getDistance() * 2.5;
    }
};

With Java 8 lambdas, this becomes one line:

Java — After (Lambda)
Function<Order, Double> feeCalculator = order -> order.getDistance() * 2.5;

One line of Java replacing 20 lines of anonymous class boilerplate. That's not just syntactic sugar — it's a paradigm shift. Lambdas make Java code more readable, more composable, and more functional. Every major Indian tech company — Swiggy, Zomato, Flipkart, Paytm, PhonePe, Razorpay — writes Java this way today.

🇮🇳 Swiggy🇮🇳 Zomato🇮🇳 Flipkart🇮🇳 Paytm🇮🇳 PhonePe🇮🇳 Razorpay
Java 8 (which introduced lambdas in March 2014) is the most adopted Java version in history. Over 75% of Java applications worldwide run on Java 8 or later. In India alone, more than 10 million developers use Java daily, and lambda proficiency is now a non-negotiable requirement in 90%+ of Java job descriptions on Naukri.com and LinkedIn India.
Section B

Learning Outcomes — Bloom's Taxonomy Mapped

Bloom's LevelLearning Outcome
🔵 RememberDefine nested class, inner class, static nested class, anonymous class, lambda expression, and functional interface
🔵 RememberList the 4 types of method references in Java with their syntax patterns
🔵 UnderstandExplain why verbose anonymous classes led to the introduction of lambda expressions in Java 8
🔵 UnderstandDescribe the relationship between functional interfaces and lambda expressions — why lambdas need exactly one abstract method
🟢 ApplyWrite lambda expressions with zero, one, and multiple parameters in different syntax forms
🟢 ApplyUse built-in functional interfaces (Predicate, Function, Consumer, Supplier) in Java programs
🟢 ApplyReplace anonymous class implementations with equivalent lambda expressions
🟢 AnalyzeCompare static nested, inner, local, and anonymous classes — determine appropriate use cases for each
🟠 AnalyzeDifferentiate between the 4 types of method references and select the correct form for a given scenario
🟠 EvaluateAssess when to use lambda expressions vs method references vs anonymous classes in production code
🟠 EvaluateCritique legacy Java codebases and identify opportunities for lambda-based refactoring
🔴 CreateDesign a functional-style data processing pipeline using lambdas and java.util.function interfaces
Section C

Concept Explanation — Nested Classes & Lambdas from Scratch

1. Static Nested Class

A static nested class is a class defined inside another class with the static keyword. It is logically grouped with the outer class but does not need an instance of the outer class to be created. Think of it as a helper class that belongs to the outer class conceptually but is independent in terms of instance creation.

📦 Static Nested Class — Structure & Use Cases

When to Use:

Use a static nested class when the inner class is a helper/utility that logically belongs to the outer class but doesn't need access to the outer class's instance variables. Common use cases: Builder pattern, configuration objects, response wrappers.

Key Rules:

• Can access static members of the outer class (including private)
Cannot directly access instance (non-static) members of the outer class
• Created without an instance of the outer class: Outer.Nested obj = new Outer.Nested();

Java
public class Restaurant {
    private static String platformName = "Swiggy";
    private String restaurantName;

    public Restaurant(String name) {
        this.restaurantName = name;
    }

    // Static nested class — does NOT need a Restaurant instance
    static class MenuItem {
        String itemName;
        double price;

        MenuItem(String itemName, double price) {
            this.itemName = itemName;
            this.price = price;
        }

        void display() {
            // Can access static member of outer class
            System.out.println(platformName + " | " + itemName + " — ₹" + price);
            // Cannot access restaurantName (non-static) — compile error!
        }
    }

    public static void main(String[] args) {
        // Created WITHOUT a Restaurant instance
        Restaurant.MenuItem item = new Restaurant.MenuItem("Chicken Biryani", 299);
        item.display();
    }
}
Swiggy | Chicken Biryani — ₹299.0

2. Non-static Inner Class

A non-static inner class (simply called an "inner class") is defined inside another class without the static keyword. It has an implicit reference to an instance of the enclosing class, meaning it can directly access all members — including private instance variables — of the outer class.

🔗 Inner Class — Bound to Outer Instance

Key Rules:

• Has an implicit reference to the enclosing outer class instance
• Can access ALL members (static + instance, including private) of the outer class
• Must be created via an outer class instance: Outer.Inner obj = outerObj.new Inner();
• Each inner class instance is tied to a specific outer class instance

Memory Implication:

Since an inner class holds a reference to the outer class, the outer class cannot be garbage collected while the inner class instance exists. This can cause memory leaks if not careful!

Java
public class Order {
    private String customerName;
    private double totalAmount;

    public Order(String customerName, double totalAmount) {
        this.customerName = customerName;
        this.totalAmount = totalAmount;
    }

    // Non-static inner class — has access to outer class fields
    class Invoice {
        private String invoiceId;

        Invoice(String invoiceId) {
            this.invoiceId = invoiceId;
        }

        void print() {
            // Directly accessing outer class's private fields!
            System.out.println("Invoice: " + invoiceId);
            System.out.println("Customer: " + customerName);
            System.out.println("Amount: ₹" + totalAmount);
        }
    }

    public static void main(String[] args) {
        Order order = new Order("Rahul Gupta", 450.0);
        // Inner class needs an outer class instance
        Order.Invoice invoice = order.new Invoice("INV-2024-001");
        invoice.print();
    }
}
Invoice: INV-2024-001 Customer: Rahul Gupta Amount: ₹450.0
Students often try to create an inner class without an outer class instance: Order.Invoice inv = new Order.Invoice("X"); — this will NOT compile. A non-static inner class ALWAYS needs an enclosing instance. Use outerObj.new Inner() syntax.

3. Local Class (Inside a Method)

A local class is declared inside the body of a method. It exists only within the scope of that method and can access local variables of the method — but only if those variables are effectively final (their value doesn't change after assignment).

Java
public class DeliveryTracker {

    void trackOrder(String orderId) {
        final String status = "In Transit";  // effectively final

        // Local class — exists only inside this method
        class GPSUpdate {
            double latitude, longitude;

            GPSUpdate(double lat, double lng) {
                this.latitude = lat;
                this.longitude = lng;
            }

            void display() {
                // Can access local variable 'status' (effectively final)
                System.out.println("Order " + orderId + " — " + status);
                System.out.println("Location: " + latitude + ", " + longitude);
            }
        }

        GPSUpdate update = new GPSUpdate(12.9716, 77.5946);
        update.display();
    }

    public static void main(String[] args) {
        new DeliveryTracker().trackOrder("SWG-88421");
    }
}
Order SWG-88421 — In Transit Location: 12.9716, 77.5946
"Effectively final" means the variable is assigned once and never modified. You don't need to write the final keyword explicitly — the compiler infers it. But if you reassign the variable anywhere, local/anonymous classes and lambdas cannot capture it.

4. Anonymous Class — The Bridge to Lambda

An anonymous class is a class with no name. It is declared and instantiated in a single expression, usually to provide a one-time implementation of an interface or abstract class. Before Java 8, anonymous classes were the primary way to pass behaviour as a parameter.

The Evolution Story: Interface → Anonymous Class → Lambda Expression

Step 1: Define an Interface

Java
interface DeliveryFeeCalculator {
    double calculate(double distance);
}

Step 2: Implement with a Named Class (Traditional)

Java
class StandardFeeCalculator implements DeliveryFeeCalculator {
    @Override
    public double calculate(double distance) {
        return distance * 2.5;
    }
}

// Usage: 3 lines of ceremony for 1 line of logic
DeliveryFeeCalculator calc = new StandardFeeCalculator();
System.out.println(calc.calculate(5.0)); // 12.5

Step 3: Implement with Anonymous Class (Java 1.1+)

Java
DeliveryFeeCalculator calc = new DeliveryFeeCalculator() {
    @Override
    public double calculate(double distance) {
        return distance * 2.5;
    }
};
System.out.println(calc.calculate(5.0)); // 12.5

Step 4: Replace with Lambda Expression (Java 8+) 🎉

Java
DeliveryFeeCalculator calc = distance -> distance * 2.5;
System.out.println(calc.calculate(5.0)); // 12.5

From 7 lines (anonymous class) to 1 line (lambda). The compiler infers the interface type, the method name, the parameter type, and the return type — all from context.

Lambda = Swiggy delivery instruction — a one-time task to an anonymous partner. You don't know the delivery partner's name. You just say "Pick up from restaurant X, deliver to address Y." That's a lambda: a disposable, single-use function handed to someone who just executes it. The delivery partner (functional interface) has one job (deliver()), and the instruction (lambda) tells them exactly what to do.

5. Functional Interface — The Foundation of Lambdas

A functional interface is an interface with exactly one abstract method. It can have any number of default methods and static methods, but only one abstract method. The @FunctionalInterface annotation is optional but recommended — it tells the compiler to enforce the single-abstract-method rule.

⚡ @FunctionalInterface — Rules & Validation

Rules:

• Exactly one abstract method (SAM — Single Abstract Method)
• Can have any number of default methods
• Can have any number of static methods
• Methods inherited from Object (like toString(), equals()) don't count
@FunctionalInterface is optional but triggers compile-time validation

Why it matters:

Lambda expressions can ONLY be assigned to functional interface types. If an interface has 2+ abstract methods, you cannot use a lambda for it.

Java
// Custom functional interface
@FunctionalInterface
interface PriceCalculator {
    double compute(double basePrice, int quantity);

    // default method — does NOT break @FunctionalInterface
    default double withGST(double basePrice, int qty) {
        return compute(basePrice, qty) * 1.18;
    }

    // static method — does NOT break @FunctionalInterface
    static String currency() { return "INR"; }
}

// Usage with lambda
PriceCalculator totalPrice = (price, qty) -> price * qty;
System.out.println(totalPrice.compute(299, 3));      // 897.0
System.out.println(totalPrice.withGST(299, 3));    // 1058.46
897.0 1058.46

Common Built-in Functional Interfaces (Preview)

InterfaceMethodPurposeExample
Runnablerun()Execute action, no input/outputThread tasks
Comparator<T>compare(T, T)Compare two objectsSorting
Predicate<T>test(T)Boolean conditionFiltering
Function<T,R>apply(T)Transform T → RMapping
Consumer<T>accept(T)Consume input, no returnPrinting
Supplier<T>get()Supply value, no inputFactory/generator

6. Lambda Expression Syntax — All Forms

A lambda expression is an anonymous function: no name, a parameter list, an arrow (->), and a body. Java infers the types from context (target typing).

🎯 Lambda Syntax Forms — Complete Reference

Form 1: No Parameters
Java
Runnable greet = () -> System.out.println("Namaste!");
greet.run();  // Namaste!
Form 2: Single Parameter (parentheses optional)
Java
// With parentheses
Function<Integer, Integer> square = (x) -> x * x;

// Without parentheses — valid for single parameter
Function<Integer, Integer> cube = x -> x * x * x;

System.out.println(square.apply(5));  // 25
System.out.println(cube.apply(3));    // 27
Form 3: Multiple Parameters
Java
BiFunction<Double, Double, Double> add = (a, b) -> a + b;
System.out.println(add.apply(10.5, 20.3));  // 30.8
Form 4: Block Body (with curly braces and explicit return)
Java
BiFunction<Integer, Integer, String> maxFinder = (a, b) -> {
    if (a > b) {
        return "First is greater: " + a;
    } else {
        return "Second is greater: " + b;
    }
};
System.out.println(maxFinder.apply(42, 17));
// First is greater: 42
Block body lambdas MUST have an explicit return statement if the functional interface's method has a return type. Expression lambdas (x -> x * 2) have an implicit return. Block lambdas (x -> { return x * 2; }) require return. Forgetting this is the #1 lambda compile error.

7. Lambda in Action — Runnable, Comparator, Custom FI

Lambda with Runnable — Thread Creation

Java
public class LambdaRunnable {
    public static void main(String[] args) {
        // Old way — anonymous class
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread 1 running (anonymous class)");
            }
        });

        // New way — lambda
        Thread t2 = new Thread(() -> System.out.println("Thread 2 running (lambda)"));

        t1.start();
        t2.start();
    }
}
Thread 1 running (anonymous class) Thread 2 running (lambda)

Lambda with Comparator — Sorting Lists

Java
import java.util.*;

public class LambdaComparator {
    public static void main(String[] args) {
        List<String> restaurants = Arrays.asList(
            "Biryani Blues", "Dominos", "Annapurna", "Zaitoon"
        );

        // Sort alphabetically using lambda
        restaurants.sort((a, b) -> a.compareTo(b));
        System.out.println("A-Z: " + restaurants);

        // Sort by string length using lambda
        restaurants.sort((a, b) -> a.length() - b.length());
        System.out.println("By length: " + restaurants);
    }
}
A-Z: [Annapurna, Biryani Blues, Dominos, Zaitoon] By length: [Dominos, Zaitoon, Annapurna, Biryani Blues]

Lambda with Custom Functional Interface

Java
@FunctionalInterface
interface DiscountPolicy {
    double applyDiscount(double price);
}

public class SwiggyDiscount {
    static double finalPrice(double price, DiscountPolicy policy) {
        return policy.applyDiscount(price);
    }

    public static void main(String[] args) {
        DiscountPolicy flat20  = price -> price * 0.80;
        DiscountPolicy flat50  = price -> price - 50;
        DiscountPolicy noDisc  = price -> price;

        System.out.println("20% off ₹500: ₹" + finalPrice(500, flat20));
        System.out.println("₹50 off ₹500: ₹" + finalPrice(500, flat50));
        System.out.println("No discount ₹500: ₹" + finalPrice(500, noDisc));
    }
}
20% off ₹500: ₹400.0 ₹50 off ₹500: ₹450.0 No discount ₹500: ₹500.0

8. Method References — Lambda Shorthand (::)

A method reference is a shorthand notation for a lambda expression that calls an existing method. If a lambda simply calls a method and passes its parameters through, you can replace it with a method reference using the :: operator.

Type 1: Static Method Reference — ClassName::staticMethod

Java
import java.util.function.Function;

public class StaticMethodRef {
    public static void main(String[] args) {
        // Lambda version
        Function<String, Integer> parser1 = s -> Integer.parseInt(s);

        // Method reference version
        Function<String, Integer> parser2 = Integer::parseInt;

        System.out.println(parser1.apply("42"));  // 42
        System.out.println(parser2.apply("99"));  // 99
    }
}
42 99

Type 2: Instance Method Reference (Bound) — instance::method

Java
import java.util.*;
import java.util.function.Consumer;

public class InstanceMethodRef {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("Biryani", "Dosa", "Paneer Tikka");

        // Lambda version
        items.forEach(item -> System.out.println(item));

        // Method reference — System.out is the instance
        items.forEach(System.out::println);
    }
}
Biryani Dosa Paneer Tikka Biryani Dosa Paneer Tikka

Type 3: Constructor Reference — ClassName::new

Java
import java.util.function.Supplier;
import java.util.function.Function;
import java.util.ArrayList;

public class ConstructorRef {
    public static void main(String[] args) {
        // No-arg constructor reference
        Supplier<ArrayList<String>> listMaker = ArrayList::new;
        ArrayList<String> list = listMaker.get();
        list.add("Swiggy");
        list.add("Zomato");
        System.out.println(list);

        // One-arg constructor reference
        Function<String, StringBuilder> sbMaker = StringBuilder::new;
        StringBuilder sb = sbMaker.apply("Hello Lambda!");
        System.out.println(sb);
    }
}
[Swiggy, Zomato] Hello Lambda!

Type 4: Arbitrary Instance Method — ClassName::instanceMethod

Java
import java.util.*;

public class ArbitraryInstanceRef {
    public static void main(String[] args) {
        List<String> cities = Arrays.asList("bangalore", "mumbai", "delhi", "pune");

        // Lambda version
        cities.replaceAll(s -> s.toUpperCase());

        // Equivalent method reference — calls toUpperCase on each String
        // cities.replaceAll(String::toUpperCase);

        System.out.println(cities);

        // Sorting with arbitrary instance method ref
        List<String> names = Arrays.asList("Zomato", "Swiggy", "Dunzo");
        names.sort(String::compareToIgnoreCase);
        System.out.println(names);
    }
}
[BANGALORE, MUMBAI, DELHI, PUNE] [Dunzo, Swiggy, Zomato]

Method Reference Summary Table

TypeSyntaxLambda EquivalentExample
StaticClassName::staticMethodx -> ClassName.method(x)Integer::parseInt
Bound Instanceinstance::methodx -> instance.method(x)System.out::println
ConstructorClassName::new() -> new ClassName()ArrayList::new
Arbitrary InstanceClassName::instanceMethod(obj, args) -> obj.method(args)String::toLowerCase

9. java.util.function — The Standard Toolkit

Java 8 introduced the java.util.function package with dozens of ready-made functional interfaces. You rarely need to define your own — these cover 95% of use cases.

Predicate<T> — Boolean Test / Filtering

Java
import java.util.function.Predicate;
import java.util.*;
import java.util.stream.Collectors;

public class PredicateDemo {
    public static void main(String[] args) {
        Predicate<Integer> isAdult = age -> age >= 18;
        Predicate<String> isVeg = item -> item.startsWith("Paneer") || item.startsWith("Dal");

        System.out.println("Is 20 adult? " + isAdult.test(20));   // true
        System.out.println("Is 15 adult? " + isAdult.test(15));   // false

        // Chaining predicates with and(), or(), negate()
        Predicate<Integer> isSenior = age -> age >= 60;
        Predicate<Integer> isWorkingAge = isAdult.and(isSenior.negate());
        System.out.println("Is 30 working age? " + isWorkingAge.test(30)); // true
        System.out.println("Is 65 working age? " + isWorkingAge.test(65)); // false

        // Filtering a list
        List<String> menu = Arrays.asList("Paneer Tikka", "Chicken 65", "Dal Makhani", "Fish Fry");
        List<String> vegItems = menu.stream().filter(isVeg).collect(Collectors.toList());
        System.out.println("Veg items: " + vegItems);
    }
}
Is 20 adult? true Is 15 adult? false Is 30 working age? true Is 65 working age? false Veg items: [Paneer Tikka, Dal Makhani]

Function<T, R> — Transformation

Java
import java.util.function.Function;

public class FunctionDemo {
    public static void main(String[] args) {
        Function<String, Integer> strLength = s -> s.length();
        Function<Integer, String> toRupees = amt -> "₹" + amt;

        System.out.println(strLength.apply("Swiggy"));   // 6
        System.out.println(toRupees.apply(299));        // ₹299

        // Chaining with andThen and compose
        Function<Integer, Integer> doubleIt = x -> x * 2;
        Function<Integer, Integer> addTen  = x -> x + 10;

        // andThen: first doubleIt, then addTen → (5 * 2) + 10 = 20
        System.out.println(doubleIt.andThen(addTen).apply(5));

        // compose: first addTen, then doubleIt → (5 + 10) * 2 = 30
        System.out.println(doubleIt.compose(addTen).apply(5));
    }
}
6 ₹299 20 30

Consumer<T> — Consume & Act

Java
import java.util.function.Consumer;
import java.util.Arrays;
import java.util.List;

public class ConsumerDemo {
    public static void main(String[] args) {
        Consumer<String> printUpper = s -> System.out.println(s.toUpperCase());
        Consumer<String> printLength = s -> System.out.println("Length: " + s.length());

        // Single consumer
        printUpper.accept("biryani");  // BIRYANI

        // Chaining consumers with andThen
        Consumer<String> printBoth = printUpper.andThen(printLength);
        printBoth.accept("paneer tikka");

        // Using with forEach
        List<String> dishes = Arrays.asList("Dosa", "Idli", "Vada");
        dishes.forEach(printUpper);
    }
}
BIRYANI PANEER TIKKA Length: 12 DOSA IDLI VADA

Supplier<T> — Generate / Factory

Java
import java.util.function.Supplier;
import java.util.Random;

public class SupplierDemo {
    public static void main(String[] args) {
        Supplier<String> orderIdGen = () -> "SWG-" + new Random().nextInt(100000);
        Supplier<Double> randomPrice = () -> 99 + new Random().nextDouble() * 400;

        System.out.println("Order 1: " + orderIdGen.get());
        System.out.println("Order 2: " + orderIdGen.get());
        System.out.println("Random price: ₹" + String.format("%.2f", randomPrice.get()));
    }
}
Order 1: SWG-48291 Order 2: SWG-73640 Random price: ₹342.18

BiFunction<T, U, R> — Two-Argument Transformation

Java
import java.util.function.BiFunction;

public class BiFunctionDemo {
    public static void main(String[] args) {
        BiFunction<Double, Integer, Double> totalPrice =
            (unitPrice, qty) -> unitPrice * qty;

        BiFunction<String, String, String> fullAddress =
            (street, city) -> street + ", " + city + ", India";

        System.out.println("Total: ₹" + totalPrice.apply(249.0, 3));
        System.out.println(fullAddress.apply("MG Road", "Bangalore"));
    }
}
Total: ₹747.0 MG Road, Bangalore, India

java.util.function — Master Reference Table

InterfaceMethod SignaturePurposeReal Use Case
Predicate<T>boolean test(T t)Test a conditionFilter veg items from menu
Function<T,R>R apply(T t)Transform T to RConvert price to string "₹299"
Consumer<T>void accept(T t)Consume/processPrint each order to console
Supplier<T>T get()Generate/supplyGenerate unique order IDs
BiFunction<T,U,R>R apply(T t, U u)Two-arg transformCalculate total = price × qty
When in doubt, use the built-in functional interfaces. Creating custom @FunctionalInterfaces is fine for domain-specific APIs (like DiscountPolicy), but for general-purpose lambdas, Predicate, Function, Consumer, and Supplier are almost always sufficient. Indian interviewers love asking about these — know their method names by heart.
The java.util.function package contains 43 functional interfaces including specialised variants like IntPredicate, LongFunction, DoubleConsumer, etc. These primitive variants avoid autoboxing overhead and are used in performance-critical systems like Swiggy's real-time order routing engine.
Section D

Learn by Doing — 3-Tier Lab Structure

🟢 Tier 1 — GUIDED TASK: Refactor Zomato Restaurant Sorter from Anonymous Classes to Lambdas

⏱️ 60–90 minutesBeginnerZero prior lambda knowledge assumed

Step 1: Create the Restaurant Class

Java
class Restaurant {
    String name;
    double rating;
    int deliveryTime; // minutes
    double minOrder;

    Restaurant(String name, double rating, int deliveryTime, double minOrder) {
        this.name = name;
        this.rating = rating;
        this.deliveryTime = deliveryTime;
        this.minOrder = minOrder;
    }

    public String toString() {
        return name + " (⭐" + rating + ", " + deliveryTime + "min, ₹" + minOrder + ")";
    }
}

Step 2: Create a List of Restaurants

Java
import java.util.*;

public class ZomatoSorter {
    public static void main(String[] args) {
        List<Restaurant> restaurants = new ArrayList<>(Arrays.asList(
            new Restaurant("Biryani Blues", 4.3, 35, 199),
            new Restaurant("Dominos", 4.1, 25, 99),
            new Restaurant("Paradise", 4.5, 40, 249),
            new Restaurant("Annapurna", 4.0, 20, 149),
            new Restaurant("Meghana Foods", 4.6, 30, 299)
        ));

        // STEP 3: OLD WAY — Sort by rating (anonymous class)
        restaurants.sort(new Comparator<Restaurant>() {
            @Override
            public int compare(Restaurant r1, Restaurant r2) {
                return Double.compare(r2.rating, r1.rating);
            }
        });
        System.out.println("By Rating (old): " + restaurants);

        // STEP 4: NEW WAY — Sort by rating (lambda)
        restaurants.sort((r1, r2) -> Double.compare(r2.rating, r1.rating));
        System.out.println("By Rating (lambda): " + restaurants);

        // STEP 5: Sort by delivery time (lambda)
        restaurants.sort((r1, r2) -> r1.deliveryTime - r2.deliveryTime);
        System.out.println("By Speed: " + restaurants);

        // STEP 6: Sort by name (method reference)
        restaurants.sort(Comparator.comparing(r -> r.name));
        System.out.println("By Name: " + restaurants);
    }
}

🎉 Congratulations! You've refactored from verbose anonymous classes to clean lambda expressions. Notice how each sort is now ONE line instead of FIVE.

🟡 Tier 2 — SEMI-GUIDED TASK: Build a Swiggy Order Processing Pipeline

⏱️ 90–120 minutesIntermediateHints provided, you fill the gaps

Your Mission:

Build an order processing pipeline for Swiggy using Predicate, Function, Consumer, and Supplier.

Requirements:

  1. Create an Order class with fields: orderId, customerName, items (List<String>), totalAmount, isPrepaid
  2. Use a Supplier<String> to auto-generate order IDs (format: "SWG-XXXXX")
  3. Use a Predicate<Order> to filter orders above ₹500 (eligible for free delivery)
  4. Use a Function<Order, Double> to calculate delivery fee (₹0 if above ₹500, else distance × ₹3)
  5. Use a Consumer<Order> to print order summary
  6. Chain them into a processing pipeline using streams

Skeleton Code:

Java
// Hint 1: Supplier for order ID
Supplier<String> idGenerator = () -> "SWG-" + /* generate random 5-digit number */;

// Hint 2: Predicate for free delivery eligibility
Predicate<Order> freeDelivery = order -> /* check if totalAmount >= 500 */;

// Hint 3: Function for delivery fee calculation
Function<Order, Double> deliveryFee = order -> /* if free, return 0; else return distance * 3 */;

// Hint 4: Consumer for printing
Consumer<Order> printOrder = order -> System.out.println(/* format order details */);
Stretch Goal: Add a BiFunction<Order, String, Order> that applies a coupon code. E.g., "SWIGGY50" gives ₹50 off, "FIRSTORDER" gives 20% off.

🔴 Tier 3 — OPEN CHALLENGE: Food Delivery Pricing Engine with Lambdas

⏱️ 2–3 hoursAdvancedNo instructions — real-world mini-project

The Brief:

Design and implement a complete food delivery pricing engine using lambdas, method references, and java.util.function interfaces. Your system should:

  1. Dynamic Pricing: Use Function<Order, Double> for base price, surge pricing (based on time of day), and distance-based delivery fee
  2. Discount Engine: Use Predicate<Order> to check eligibility, Function<Double, Double> to apply discounts. Support stacking multiple discounts using andThen()
  3. Tax Calculator: Use BiFunction<Double, String, Double> to calculate GST based on state (5% for food, 18% for packaged items)
  4. Order Summary: Use Consumer<Order> to print a formatted bill
  5. Order Factory: Use Supplier<Order> to generate test orders
  6. Coupon Validator: Use method references for string validation

Deliverable: A complete Java file that can process 10 random orders through the entire pipeline, printing detailed bills for each.

This pricing engine pattern is exactly what Swiggy and Zomato use. A polished version of this project on your GitHub demonstrates real-world Java skills and can directly land you internship interviews.
Section E

Problem Set — Syntax, Programming, Industry & Interview

Syntax Questions (5)

  1. Beginner Write the lambda expression equivalent of: Comparator<String> comp = new Comparator<String>() { public int compare(String a, String b) { return a.length() - b.length(); } };
  2. Beginner What is the correct lambda syntax for a Runnable that prints "Hello Lambda"? Why are empty parentheses () required?
  3. Intermediate Convert this lambda to a method reference: list.forEach(s -> System.out.println(s));
  4. Intermediate Write a Predicate<String> lambda that returns true if a string has more than 5 characters AND starts with "A". Use the and() method.
  5. Advanced Write a Function<Function<Integer,Integer>, Function<Integer,Integer>> lambda that takes a function and returns a new function that applies the original function twice. Example: if input is x -> x + 1, output should be x -> x + 2.

Programming Questions (8)

  1. Beginner Write a Java program that sorts a list of Indian city names by length using a lambda expression with Comparator.
  2. Beginner Create a custom functional interface Greeting with method String greet(String name). Implement it using a lambda that returns "Namaste, " + name + "!".
  3. Intermediate Write a program using Predicate<Integer> that filters even numbers, then uses Function<Integer, Integer> to square them, and Consumer<Integer> to print results.
  4. Intermediate Implement a Supplier<List<String>> that generates a list of 5 random Indian food items. Use it to populate and print 3 different menus.
  5. Intermediate Write a program that uses all 4 types of method references (static, bound instance, constructor, arbitrary instance) in a single class.
  6. Advanced Build a Function<List<Integer>, Map<String, List<Integer>>> that categorises numbers into "Even" and "Odd" groups using lambdas and streams.
  7. Advanced Create a mini calculator using BiFunction<Double, Double, Double> stored in a Map<String, BiFunction> for operations: add, subtract, multiply, divide.
  8. Advanced Write a chain of Functions using andThen() and compose() that: (1) trims a string, (2) converts to uppercase, (3) appends " — PROCESSED". Apply to a list of messy inputs.

Industry Questions (3)

  1. Intermediate Swiggy uses lambdas in its pricing engine. Design the functional interfaces needed for: base price calculation, surge multiplier, coupon discount, and GST. Show how they chain together.
  2. Advanced Flipkart's product recommendation engine filters products by category, price range, and rating. Design this using Predicate<Product> with and(), or(), and negate() chaining.
  3. Advanced Razorpay processes payments through validation steps. Design a pipeline using Function.andThen() that: validates amount → checks fraud → applies charges → generates receipt.

Interview Questions (3)

  1. Intermediate TCS/Infosys: "What is the difference between a lambda expression and an anonymous class? Can you always replace one with the other?"
  2. Advanced Flipkart/Amazon: "Explain the concept of 'effectively final' in the context of lambda expressions. Why is this restriction necessary?"
  3. Advanced Google/Microsoft: "What happens at the bytecode level when Java compiles a lambda expression? How is it different from an anonymous class?"
Section F

MCQ Assessment Bank — 30 Questions (Bloom's Mapped)

Remember / Identify (Q1–Q5)

Q1

A functional interface in Java must have exactly:

  1. Zero abstract methods
  2. One abstract method
  3. Two abstract methods
  4. Any number of abstract methods
Remember
✅ Answer: (B) One abstract method — A functional interface (also called SAM interface) has exactly one abstract method. It can have any number of default and static methods.
Q2

Which annotation is used to explicitly declare a functional interface?

  1. @Lambda
  2. @FunctionalInterface
  3. @SingleMethod
  4. @Interface
Remember
✅ Answer: (B) @FunctionalInterface — This annotation is optional but recommended. It triggers a compile-time error if the interface has more than one abstract method.
Q3

The arrow operator in a lambda expression is:

  1. =>
  2. ->
  3. >>
  4. ::
Remember
✅ Answer: (B) -> — The arrow operator separates the parameter list from the lambda body. Note: Java uses -> (not => like JavaScript).
Q4

Which of the following is NOT a type of method reference in Java?

  1. Static method reference
  2. Constructor reference
  3. Abstract method reference
  4. Arbitrary instance method reference
Remember
✅ Answer: (C) Abstract method reference — The four types are: static method, bound instance method, constructor, and arbitrary instance method. "Abstract method reference" is not a valid type.
Q5

A static nested class can access which members of its outer class?

  1. Both static and instance members
  2. Only static members (including private static)
  3. Only public members
  4. No members of the outer class
Remember
✅ Answer: (B) Only static members — A static nested class does not have a reference to an instance of the outer class, so it can only access static members (including private static).

Understand / Explain (Q6–Q10)

Q6

Why can a lambda expression only be used with a functional interface?

  1. Because lambdas can only have one parameter
  2. Because the compiler needs exactly one abstract method to determine what the lambda implements
  3. Because functional interfaces are faster than regular interfaces
  4. Because lambdas cannot implement default methods
Understand
✅ Answer: (B) — The compiler uses the single abstract method of the functional interface to infer the parameter types, return type, and checked exceptions of the lambda. With multiple abstract methods, it wouldn't know which one the lambda is implementing.
Q7

What does "effectively final" mean in the context of lambda expressions?

  1. The variable is declared with the final keyword
  2. The variable's value is never changed after initialisation, even without the final keyword
  3. The variable is a constant defined at class level
  4. The variable is passed as a parameter to the lambda
Understand
✅ Answer: (B) — A variable is "effectively final" if its value is assigned once and never modified. Lambdas can capture local variables only if they are effectively final — this prevents concurrency issues since lambdas may execute on different threads.
Q8

Why does a non-static inner class hold an implicit reference to its outer class?

  1. To allow the inner class to be serialised
  2. To enable the inner class to access instance members of the outer class
  3. To make the inner class thread-safe
  4. To improve performance of method calls
Understand
✅ Answer: (B) — A non-static inner class needs to access instance fields and methods of the outer class. To do this, the compiler automatically creates a hidden reference to the enclosing outer class instance.
Q9

What is the primary advantage of method references over lambda expressions?

  1. They execute faster at runtime
  2. They can do things lambdas cannot
  3. They improve readability when the lambda simply calls an existing method
  4. They support multiple method calls
Understand
✅ Answer: (C) — Method references are syntactic sugar for lambdas. They improve readability when the lambda's sole purpose is to call an existing method. System.out::println is clearer than x -> System.out.println(x).
Q10

Which java.util.function interface would you use to check if an order amount is above ₹500?

  1. Function<Order, Boolean>
  2. Predicate<Order>
  3. Consumer<Order>
  4. Supplier<Boolean>
Understand
✅ Answer: (B) Predicate<Order> — Predicate is designed for boolean conditions. While Function<Order, Boolean> would technically work, Predicate is the semantically correct choice and has useful methods like and(), or(), negate().

Apply / Implement (Q11–Q15)

Q11

What is the output of: Function<Integer, Integer> f = x -> x * 2; System.out.println(f.apply(5));

  1. 5
  2. 10
  3. Compilation error
  4. 25
Apply
✅ Answer: (B) 10 — The lambda takes an Integer and returns it multiplied by 2. f.apply(5) returns 5 * 2 = 10.
Q12

Which lambda correctly implements BiFunction<String, String, Integer>?

  1. (a, b) -> a + b
  2. (a, b) -> a.length() + b.length()
  3. a -> a.length()
  4. (a, b, c) -> a.length()
Apply
✅ Answer: (B) — BiFunction<String, String, Integer> takes two Strings and returns an Integer. Option B returns the sum of both string lengths (an Integer). Option A returns a String (concatenation). Option C has only one parameter. Option D has three parameters.
Q13

What is the method reference equivalent of: list.forEach(s -> s.toUpperCase())?

  1. list.forEach(String::toUpperCase)
  2. list.forEach(s::toUpperCase)
  3. list.forEach(String.toUpperCase)
  4. This lambda cannot be converted to a method reference because forEach expects a Consumer, but toUpperCase returns a String
Apply
✅ Answer: (D) — Tricky! forEach takes a Consumer<String> (void accept), but the lambda calls toUpperCase() which returns a String. The lambda itself returns void (the String is discarded), so it works as a Consumer. However, String::toUpperCase would also work here since the return value is simply ignored. In practice, answer (A) would compile, but the intent is wasteful since the result is discarded.
Q14

What does this code print? Predicate<String> p = s -> s.length() > 3; System.out.println(p.negate().test("Hi"));

  1. true
  2. false
  3. Compilation error
  4. Runtime exception
Apply
✅ Answer: (A) true — The predicate p tests if length > 3. "Hi" has length 2, so p.test("Hi") = false. negate() inverts it, so p.negate().test("Hi") = true.
Q15

What is the correct way to create a Thread using a lambda?

  1. Thread t = () -> System.out.println("Run");
  2. Thread t = new Thread(() -> System.out.println("Run"));
  3. Thread t = new Thread(-> System.out.println("Run"));
  4. Thread t = new Runnable(() -> System.out.println("Run"));
Apply
✅ Answer: (B) — Thread constructor accepts a Runnable. The lambda () -> System.out.println("Run") implements Runnable's run() method. Option A tries to assign a lambda directly to Thread type. Option C is missing parentheses for zero params. Option D uses new Runnable() which is not how you create threads.

Analyze / Compare (Q16–Q20)

Q16

When should you use a static nested class instead of a non-static inner class?

  1. When the nested class needs to access instance variables of the outer class
  2. When the nested class doesn't need a reference to an outer class instance and is a logically grouped utility
  3. When you need to create multiple instances of the nested class
  4. When the nested class needs to be serialised
Analyze
✅ Answer: (B) — Use static nested classes when there's no dependency on outer class instance state. This avoids the hidden outer reference, reducing memory usage and preventing potential memory leaks. Common use: Builder pattern, configuration objects, helper data structures.
Q17

What is the key difference between Function<T, R> and Consumer<T>?

  1. Function takes no parameters, Consumer takes one
  2. Function returns a value (R), Consumer returns void
  3. Consumer can be chained, Function cannot
  4. Function is for primitive types only
Analyze
✅ Answer: (B) — Function<T, R> transforms input T into output R (apply method returns R). Consumer<T> processes input T without returning anything (accept method returns void). Both can be chained — Function with andThen/compose, Consumer with andThen.
Q18

Why might an anonymous class be preferred over a lambda expression?

  1. When you need to implement an interface with multiple abstract methods
  2. When you need better performance
  3. When the code needs to run on Java 7
  4. Both A and C
Analyze
✅ Answer: (D) — Lambdas can only implement functional interfaces (one abstract method). For interfaces with multiple abstract methods or abstract classes, you must use anonymous classes. Also, lambdas require Java 8+, so pre-Java 8 codebases must use anonymous classes.
Q19

In String::compareToIgnoreCase, what type of method reference is this?

  1. Static method reference
  2. Bound instance method reference
  3. Constructor reference
  4. Arbitrary instance method reference
Analyze
✅ Answer: (D) Arbitrary instance method reference — compareToIgnoreCase is an instance method of String. In ClassName::instanceMethod form, the first argument becomes the object on which the method is invoked. It's equivalent to (s1, s2) -> s1.compareToIgnoreCase(s2).
Q20

What is the difference between f.andThen(g) and f.compose(g) for Function f and g?

  1. No difference — they are the same
  2. andThen applies f first then g; compose applies g first then f
  3. andThen is for Function, compose is for Consumer
  4. andThen chains sequentially, compose chains in parallel
Analyze
✅ Answer: (B) — f.andThen(g) = g(f(x)) — apply f first, then g to the result. f.compose(g) = f(g(x)) — apply g first, then f to the result. The execution order is opposite.

Evaluate / Justify (Q21–Q25)

Q21

A developer writes: int x = 10; Runnable r = () -> System.out.println(x); x = 20; — What happens?

  1. Prints 10
  2. Prints 20
  3. Compilation error — x is not effectively final
  4. Runtime exception
Evaluate
✅ Answer: (C) Compilation error — The variable x is modified after being captured by the lambda (x = 20), making it NOT effectively final. Lambdas can only capture local variables that are effectively final.
Q22

Which approach is most appropriate for a one-time Comparator in a sort call?

  1. Define a separate class implementing Comparator
  2. Use an anonymous class
  3. Use a lambda expression
  4. Use a local class inside the method
Evaluate
✅ Answer: (C) Lambda expression — For a one-time, simple comparison, a lambda is the most concise and readable. A separate class is overkill. An anonymous class is verbose. A local class adds unnecessary complexity.
Q23

A team is debating whether to replace ALL anonymous classes with lambdas during a codebase refactoring. What is the correct evaluation?

  1. Replace all — lambdas are always better
  2. Replace only those implementing functional interfaces — anonymous classes extending abstract classes or implementing multi-method interfaces cannot be replaced
  3. Don't replace any — anonymous classes are more readable
  4. Replace only if the project uses Java 11+
Evaluate
✅ Answer: (B) — Lambdas can only replace anonymous classes that implement functional interfaces (one abstract method). Anonymous classes that extend abstract classes, implement interfaces with multiple abstract methods, or use `this` to refer to themselves cannot be replaced with lambdas.
Q24

A developer uses Predicate<String> p = s -> { return s != null && s.length() > 0; }; — How can this be improved?

  1. Use expression lambda: s -> s != null && s.length() > 0
  2. Use method reference: String::isEmpty
  3. Use Predicate.not(String::isEmpty) combined with Objects::nonNull
  4. Both A and C are valid improvements; C is most idiomatic
Evaluate
✅ Answer: (D) — Option A removes unnecessary block body. Option C uses built-in utilities: Predicate.not(String::isEmpty) combined with a null check is the most idiomatic Java 11+ approach. Both are improvements over the original.
Q25

Is it valid to define an interface with one abstract method and three default methods, and annotate it with @FunctionalInterface?

  1. No — default methods count as abstract methods
  2. Yes — only abstract methods count; default methods are excluded
  3. No — @FunctionalInterface requires zero default methods
  4. Yes — but only if the default methods are private
Evaluate
✅ Answer: (B) — A functional interface requires exactly one abstract method. Default methods have implementations, so they don't count. You can have unlimited default and static methods alongside the single abstract method.

Create / Design (Q26–Q30)

Q26

You need to design a data pipeline: filter orders → calculate tax → format receipt. Which chain of functional interfaces is correct?

  1. Consumer → Function → Supplier
  2. Predicate → Function → Consumer
  3. Supplier → Predicate → Function
  4. Function → Consumer → Predicate
Create
✅ Answer: (B) — Predicate filters (boolean test), Function transforms (calculate tax), Consumer processes final output (print/save receipt). This is the standard filter-map-forEach pipeline pattern.
Q27

To create a flexible discount system where different discount strategies can be plugged in at runtime, which design is most appropriate?

  1. Use inheritance with abstract DiscountCalculator class
  2. Use a Function<Double, Double> parameter that accepts different lambda implementations
  3. Use if-else statements for each discount type
  4. Use enum with switch-case
Create
✅ Answer: (B) — Using Function<Double, Double> as a strategy parameter allows different discount lambdas to be passed at runtime without creating new classes. This is the Strategy pattern implemented functionally. Example: applyDiscount(price, p -> p * 0.8) for 20% off.
Q28

You want to create a validator that checks: (1) not null, (2) not empty, (3) matches regex pattern. How would you compose this with Predicate?

  1. Write three separate if statements
  2. Create one Predicate with all logic in a single lambda body
  3. Create three Predicates and chain with .and(): notNull.and(notEmpty).and(matchesPattern)
  4. Use a for loop over a list of validation rules
Create
✅ Answer: (C) — Predicate chaining with .and() is the most modular and reusable approach. Each predicate is independently testable, and they compose cleanly. This is exactly how validation frameworks like Bean Validation work internally.
Q29

You need a factory method that creates different types of notifications (SMS, Email, Push). Which functional interface is best suited?

  1. Predicate<String>
  2. Consumer<String>
  3. Function<String, Notification>
  4. Supplier<Notification>
Create
✅ Answer: (C) Function<String, Notification> — Since the factory takes an input (notification type as String) and produces an output (Notification object), Function<String, Notification> is the right choice. Supplier would work only for no-argument factories.
Q30

Design a logging decorator using Function composition. Given Function<Request, Response> handler, how would you add logging before and after?

  1. Subclass the handler and override apply()
  2. Wrap with a new Function that logs, calls handler.apply(), logs again, and returns the result
  3. Use handler.andThen(logFunction) only
  4. Use AOP (Aspect-Oriented Programming) exclusively
Create
✅ Answer: (B) — Create a decorator Function that wraps the original: Function<Request, Response> logged = req -> { log("Before"); Response res = handler.apply(req); log("After"); return res; }; This is the Decorator pattern implemented functionally. Option C only logs after, not before.
Section G

Short Answer Questions (8)

Q1. What is a functional interface? Give two examples from the Java standard library.

Answer: A functional interface is an interface with exactly one abstract method (SAM — Single Abstract Method). It can have any number of default and static methods. The @FunctionalInterface annotation can be used to enforce this at compile time.

Examples: (1) java.lang.Runnable with method void run(). (2) java.util.Comparator<T> with method int compare(T o1, T o2). Other examples include Predicate<T>, Function<T,R>, Consumer<T>, and Supplier<T>.

Q2. List three differences between a lambda expression and an anonymous class.

Answer:

AspectLambda ExpressionAnonymous Class
TypeCan only implement functional interfaces (1 abstract method)Can implement any interface or extend abstract classes
this keywordRefers to the enclosing classRefers to the anonymous class itself
CompilationUses invokedynamic bytecode instruction (lighter)Generates a separate .class file (heavier)

Q3. Name and describe the four types of method references in Java.

Answer:

(1) Static method reference (ClassName::staticMethod) — references a static method, e.g., Integer::parseInt.
(2) Bound instance method reference (instance::method) — references a method on a specific object, e.g., System.out::println.
(3) Constructor reference (ClassName::new) — references a constructor, e.g., ArrayList::new.
(4) Arbitrary instance method reference (ClassName::instanceMethod) — references an instance method where the first argument becomes the receiver, e.g., String::toLowerCase.

Q4. What does "effectively final" mean? Why is this constraint imposed on lambda expressions?

Answer: A variable is "effectively final" if its value is assigned once and never modified, even without the explicit final keyword. Lambdas can only capture effectively final local variables because: (1) Lambdas may execute on a different thread than where the variable was declared, so modifying it would cause concurrency issues. (2) Lambdas capture a copy of the variable, not the variable itself — if the original changed, the lambda's copy would be stale.

Q5. What is the purpose of the @FunctionalInterface annotation? Is it mandatory?

Answer: The @FunctionalInterface annotation serves as a compile-time safeguard. If an interface annotated with it has more than one abstract method, the compiler throws an error. It also serves as documentation, clearly communicating the developer's intent that the interface is designed for lambda use. It is not mandatory — any interface with exactly one abstract method is automatically a functional interface and can be used with lambdas, even without the annotation.

Q6. How does Predicate<T> differ from Function<T, Boolean>?

Answer: Both can represent a boolean-returning function, but they differ: (1) Predicate<T> returns primitive boolean via test(T), avoiding autoboxing overhead. Function<T, Boolean> returns the wrapper Boolean via apply(T). (2) Predicate provides composition methods: and(), or(), negate(). Function provides andThen() and compose(). (3) Predicate is semantically clearer — it explicitly communicates "this is a boolean test." Use Predicate when testing conditions; use Function<T, Boolean> rarely, only when needed for generic Function pipelines.

Q7. Differentiate between a static nested class and a non-static inner class.

Answer:

FeatureStatic Nested ClassNon-static Inner Class
Outer instance referenceNo implicit reference to outer classHolds implicit reference to outer instance
Access to outer membersOnly static members (incl. private static)All members (static + instance, incl. private)
Creation syntaxnew Outer.Nested()outerObj.new Inner()
MemoryLightweight — no outer referenceHeavier — prevents GC of outer class
Common useBuilder pattern, helpersIterators, event handlers tied to instance

Q8. When should you NOT use lambda expressions?

Answer: Avoid lambdas when: (1) Complex logic: If the lambda body exceeds 3–4 lines, extract it into a named method for readability. (2) Multiple abstract methods: The target interface has more than one abstract method (not a functional interface). (3) Need for this: When you need this to refer to the implementing object itself (lambdas' this refers to the enclosing class). (4) State management: When the implementation needs its own instance variables or constructor. (5) Extending abstract classes: Lambdas cannot extend abstract classes. (6) Debugging: Anonymous classes generate named .class files that appear in stack traces; lambda stack traces can be harder to read.

Section H

Long Answer Questions (3)

Q1. Trace the evolution from anonymous classes to lambda expressions in Java. Explain with code examples how the same functionality is expressed in both approaches, and discuss the advantages of lambdas.

Answer:

Before Java 8 — The Anonymous Class Era: Java used anonymous inner classes to pass behaviour as arguments. Consider sorting a list of employees by salary:

Java
// Step 1: Define the interface
interface SalaryComparator {
    int compare(Employee e1, Employee e2);
}

// Step 2: Anonymous class implementation (verbose!)
Collections.sort(employees, new Comparator<Employee>() {
    @Override
    public int compare(Employee e1, Employee e2) {
        return Double.compare(e1.salary, e2.salary);
    }
});

Problems with anonymous classes: (1) Verbose boilerplate — 5 lines for 1 line of logic. (2) Poor readability — the actual comparison logic is buried. (3) Generates a separate .class file for each anonymous class. (4) this refers to the anonymous class, not the enclosing class, causing confusion.

Java 8 — Lambda Revolution:

Java
// Same sort — one line!
employees.sort((e1, e2) -> Double.compare(e1.salary, e2.salary));

// Even shorter with method reference
employees.sort(Comparator.comparingDouble(e -> e.salary));

Advantages of lambdas: (1) Conciseness: Reduces boilerplate by 70–90%. (2) Readability: The intent (comparison logic) is front and center. (3) Performance: Uses invokedynamic instead of generating anonymous class files — lighter on class loading. (4) Functional composition: Lambdas can be chained using andThen(), compose(), and(), or(). (5) Streams compatibility: Lambdas unlock the Streams API for declarative data processing.

Limitation: Lambdas can only replace anonymous classes implementing functional interfaces. For multi-method interfaces or abstract classes, anonymous classes are still required.

Q2. Explain the five core interfaces in java.util.function (Predicate, Function, Consumer, Supplier, BiFunction) with detailed code examples showing real-world usage.

Answer:

The java.util.function package, introduced in Java 8, provides standardised functional interfaces that eliminate the need to define custom interfaces for common patterns.

1. Predicate<T> — Tests a condition, returns boolean.

Java
Predicate<String> isValidEmail = email -> email.contains("@") && email.endsWith(".com");
Predicate<Integer> isEligibleAge = age -> age >= 18 && age <= 60;

// Chaining: Find eligible adults with valid email
// customers.stream().filter(isEligibleAge.and(hasValidEmail))...

2. Function<T, R> — Transforms input of type T into output of type R.

Java
Function<String, String> toSlug = name -> name.toLowerCase().replace(" ", "-");
Function<Double, String> formatPrice = price -> String.format("₹%.2f", price);

// Chaining: slug then prefix
Function<String, String> urlGenerator = toSlug.andThen(slug -> "/menu/" + slug);
System.out.println(urlGenerator.apply("Chicken Biryani")); // /menu/chicken-biryani

3. Consumer<T> — Processes input without returning anything (side effects).

Java
Consumer<Order> sendSMS = order -> System.out.println("SMS sent for " + order.id);
Consumer<Order> sendEmail = order -> System.out.println("Email sent for " + order.id);
Consumer<Order> notifyAll = sendSMS.andThen(sendEmail); // both notifications

4. Supplier<T> — Generates/supplies a value with no input.

Java
Supplier<LocalDateTime> now = LocalDateTime::now;
Supplier<String> uuid = () -> UUID.randomUUID().toString();
// Lazy initialisation, factory methods, default values

5. BiFunction<T, U, R> — Takes two arguments of types T and U, returns R.

Java
BiFunction<Double, Integer, Double> calculateGST =
    (amount, gstPercent) -> amount * gstPercent / 100;
System.out.println(calculateGST.apply(1000.0, 18)); // 180.0

Real-world pipeline: In a food delivery app, you'd use Predicate to filter eligible orders, Function to calculate prices, BiFunction for GST, and Consumer to print/send the final bill — all composable, all reusable.

Q3. Design a lambda-based event handling system for a food delivery application. Describe the architecture, define the functional interfaces needed, and implement core functionality with code.

Answer:

Architecture: An event-driven system where different events (ORDER_PLACED, ORDER_ACCEPTED, DELIVERY_STARTED, DELIVERED) trigger different handlers. Instead of using traditional Observer pattern with heavyweight classes, we use lambdas as lightweight event handlers.

Java
import java.util.*;
import java.util.function.Consumer;

class OrderEvent {
    String orderId, eventType, timestamp;
    Map<String, Object> data;

    OrderEvent(String orderId, String eventType) {
        this.orderId = orderId;
        this.eventType = eventType;
        this.timestamp = java.time.LocalDateTime.now().toString();
        this.data = new HashMap<>();
    }
}

class EventBus {
    // Map of event type → list of lambda handlers
    private Map<String, List<Consumer<OrderEvent>>> handlers = new HashMap<>();

    void subscribe(String eventType, Consumer<OrderEvent> handler) {
        handlers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler);
    }

    void publish(OrderEvent event) {
        handlers.getOrDefault(event.eventType, Collections.emptyList())
                .forEach(handler -> handler.accept(event));
    }
}

public class DeliveryEventSystem {
    public static void main(String[] args) {
        EventBus bus = new EventBus();

        // Register handlers as lambdas — no classes needed!
        bus.subscribe("ORDER_PLACED",
            e -> System.out.println("📧 SMS to customer: Order " + e.orderId + " placed!"));

        bus.subscribe("ORDER_PLACED",
            e -> System.out.println("🏪 Notify restaurant for " + e.orderId));

        bus.subscribe("DELIVERED",
            e -> System.out.println("⭐ Ask for rating: " + e.orderId));

        // Publish events
        bus.publish(new OrderEvent("SWG-001", "ORDER_PLACED"));
        bus.publish(new OrderEvent("SWG-001", "DELIVERED"));
    }
}
📧 SMS to customer: Order SWG-001 placed! 🏪 Notify restaurant for SWG-001 ⭐ Ask for rating: SWG-001

Why this works: Each handler is a Consumer<OrderEvent> — a lambda. No need for EventListener classes, Observer interfaces, or factory patterns. New behaviour is added by simply subscribing another lambda. This is the architecture used by modern event-driven systems at Swiggy, Razorpay, and Flipkart.

Section I

Lab Programs

No specific university lab program is mapped to Unit 10. Lab 2 — Multithreading via Lambda is covered in Unit 11.

Practice the 3-tier labs from Section D thoroughly. The Tier 1 (Zomato Sorter refactoring) and Tier 2 (Swiggy Order Pipeline) exercises cover all the lambda concepts you'll encounter in lab exams and placements. Write each program by hand first, then type and run it. This builds muscle memory for lambda syntax.
Self-Practice Lab: Take any Java program from previous units (e.g., collection sorting, interface implementations, thread creation) and refactor it to use lambdas and method references wherever possible. Track how many lines of code you eliminate — aim for 30–50% reduction.
Section J

Industry Spotlight — A Day in the Life

👨‍💻 Vikram Sahu, 28 — Backend Developer at Swiggy, Bangalore

Background: B.Tech from NIT Bhopal (CSE, 2019). Joined Swiggy as a Graduate Software Engineer. Promoted to SDE-2 in 2022. Writes Java daily — Spring Boot microservices, Kafka event consumers, and REST APIs for the order management system.

A Typical Day:

9:30 AM — Morning standup with the Order Platform team. Review yesterday's deployment metrics — latency, error rates, order processing throughput.

10:00 AM — Code review on a PR that refactors the coupon validation service. The old code used anonymous classes for each coupon rule; the new version uses Predicate<Order> chains: isMinOrderMet.and(isNotExpired).and(isFirstTimeUser).

11:30 AM — Implement a new surge pricing function using Function<Order, Double> that considers time of day, weather API data, and restaurant load. Uses Function.andThen() to chain: base price → surge multiplier → platform fee → GST.

1:00 PM — Lunch at Swiggy's office cafeteria in EGL Tech Park, Bangalore. Discuss Kafka consumer patterns with the team.

2:00 PM — Write a Kafka event consumer using lambdas: consumer.subscribe("order-events", event -> processOrder(event));. Each order event triggers a pipeline of lambda-based handlers.

4:00 PM — Unit testing with JUnit 5. Uses lambda assertions: assertThrows(IllegalArgumentException.class, () -> validateOrder(null));

5:30 PM — Tech talk on "Functional Programming Patterns in Java 17." Vikram presents how Swiggy's pricing engine evolved from Strategy pattern (classes) to functional composition (lambdas).

DetailInfo
Tools Used DailyJava 17, Spring Boot 3, IntelliJ IDEA, Kafka, Docker, Kubernetes, Git
Entry Salary (2024)₹12–18 LPA (SDE-1 at Swiggy)
Mid-Level (3–5 yrs)₹22–35 LPA (SDE-2)
Senior (7+ yrs)₹40–60 LPA (SDE-3 / Staff)
Companies Hiring Java DevsSwiggy, Flipkart, Zomato, Razorpay, PhonePe, Paytm, Amazon, Google, Goldman Sachs, Morgan Stanley, TCS Digital, Infosys Power Programmer
Vikram's advice to students: "Don't just learn lambda syntax — understand WHY it exists. In my interview at Swiggy, they didn't ask me to write a lambda. They gave me a 50-line anonymous class and asked me to refactor it. That's the real skill — seeing where lambdas make code better."
Section K

Earn With It — Functional Refactoring Services

💰 Your Earning Path After This Unit

Portfolio Piece: "Zomato System Refactoring — From Anonymous Classes to Lambdas" — a before/after showcase of code modernisation with metrics (lines reduced, readability improved).

Service You Can Offer: Legacy Java Codebase Refactoring — Many Indian companies still run pre-Java 8 code. You can offer to modernise their codebases.

Freelance Gig Ideas:

• Refactor Java projects from anonymous classes to lambdas — ₹3,000–₹10,000/project

• Build reusable lambda-based utility libraries (validation, pricing, notification) — ₹5,000–₹15,000

• Write Java code review reports highlighting lambda refactoring opportunities — ₹2,000–₹8,000

• Create Java 8+ migration guides for small IT companies — ₹5,000–₹20,000

PlatformBest ForTypical Rate
InternshalaStudent projects, Java assignments₹2,000–₹8,000/project
FiverrJava code review & refactoring gigs$20–$100/gig (₹1,600–₹8,000)
UpworkEnterprise legacy Java modernisation$25–$60/hour
LinkedInDirect outreach to Indian IT companies₹10,000–₹30,000/project
ToptalPremium Java consultancy$60–$120/hour

⏱️ Time to First Earning: 3–4 weeks (if you complete Tier 3 lab, build a GitHub portfolio with 3 refactored projects, and create a Fiverr gig).

The Java refactoring market in India is massive. Thousands of companies (TCS, Infosys, Wipro, HCL projects) have legacy Java 6/7 codebases that need modernisation. A student who can demonstrate lambda refactoring skills has a significant advantage in placement interviews at service companies.
Section L

Chapter Summary

📋 Key Takeaways — Unit 10

  • Nested classes come in 4 flavours: static nested, inner (non-static), local, and anonymous — each with distinct access rules and use cases
  • Anonymous classes provide one-time implementations inline, but are verbose — lambdas replaced most of their use cases
  • Functional interfaces have exactly one abstract method; @FunctionalInterface enforces this at compile time
  • Lambda expressions provide concise syntax: (params) -> expression or (params) -> { statements; }
  • Method references (::) are lambda shorthand for 4 patterns: static, bound instance, constructor, arbitrary instance
  • java.util.function provides Predicate (filter), Function (transform), Consumer (process), Supplier (generate), BiFunction (two-arg transform)
  • Lambdas can only capture effectively final local variables — variables assigned once and never modified
  • Composition methods (andThen, compose, and, or, negate) enable building powerful pipelines from simple functions

🐦 Code Tweet

Lambdas = anonymous functions.

@FunctionalInterface + arrow -> = cleaner Java.

Predicate filters, Function transforms,

Consumer consumes, Supplier supplies.

Method refs (::) are lambda shorthand.

#Java8 #ModernJava #LambdaExpressions

Section M

Checkpoint — Self-Assessment

Skill / ConceptTool / TechniquePortfolio PieceEarn-Ready?
Nested Classes (4 types)Java compiler, IntelliJ✅ Yes — conceptual interview prep
Lambda ExpressionsJava 8+, any IDERefactored Zomato Sorter✅ Yes — essential for any Java job
Functional Interfaces@FunctionalInterfaceCustom DiscountPolicy interface✅ Yes — design skill for APIs
Method References:: operator, 4 typesAll-types demo program✅ Yes — code review skill
java.util.functionPredicate, Function, Consumer, Supplier, BiFunctionSwiggy Order Pipeline✅ Yes — ₹3,000–₹10,000/project
Functional CompositionandThen, compose, and, orPricing Engine✅ Yes — advanced Java gigs
Legacy RefactoringAnonymous → Lambda conversionBefore/After showcase on GitHub✅ Yes — ₹5,000–₹20,000/project
Minimum Viable Earning Setup after this unit: A GitHub portfolio with 3+ lambda-refactored Java projects + a Fiverr/LinkedIn profile offering "Java Code Modernisation" services = you can earn ₹10,000–₹30,000/month from refactoring gigs while still in college.

✅ Unit 10 complete. MCQs: 30. Ready for Unit 11!

[QR: Link to EduArtha video tutorial — Nested Classes & Lambda Expressions]