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)
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.
Learning Outcomes — Bloom's Taxonomy Mapped
| Bloom's Level | Learning Outcome |
|---|---|
| 🔵 Remember | Define nested class, inner class, static nested class, anonymous class, lambda expression, and functional interface |
| 🔵 Remember | List the 4 types of method references in Java with their syntax patterns |
| 🔵 Understand | Explain why verbose anonymous classes led to the introduction of lambda expressions in Java 8 |
| 🔵 Understand | Describe the relationship between functional interfaces and lambda expressions — why lambdas need exactly one abstract method |
| 🟢 Apply | Write lambda expressions with zero, one, and multiple parameters in different syntax forms |
| 🟢 Apply | Use built-in functional interfaces (Predicate, Function, Consumer, Supplier) in Java programs |
| 🟢 Apply | Replace anonymous class implementations with equivalent lambda expressions |
| 🟢 Analyze | Compare static nested, inner, local, and anonymous classes — determine appropriate use cases for each |
| 🟠 Analyze | Differentiate between the 4 types of method references and select the correct form for a given scenario |
| 🟠 Evaluate | Assess when to use lambda expressions vs method references vs anonymous classes in production code |
| 🟠 Evaluate | Critique legacy Java codebases and identify opportunities for lambda-based refactoring |
| 🔴 Create | Design a functional-style data processing pipeline using lambdas and java.util.function interfaces |
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
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(); } }
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
• 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
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(); } }
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"); } }
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.
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
• 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
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
Common Built-in Functional Interfaces (Preview)
| Interface | Method | Purpose | Example |
|---|---|---|---|
Runnable | run() | Execute action, no input/output | Thread tasks |
Comparator<T> | compare(T, T) | Compare two objects | Sorting |
Predicate<T> | test(T) | Boolean condition | Filtering |
Function<T,R> | apply(T) | Transform T → R | Mapping |
Consumer<T> | accept(T) | Consume input, no return | Printing |
Supplier<T> | get() | Supply value, no input | Factory/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
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)); // 27Form 3: Multiple Parameters
Java BiFunction<Double, Double, Double> add = (a, b) -> a + b; System.out.println(add.apply(10.5, 20.3)); // 30.8Form 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
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(); } }
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); } }
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)); } }
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 } }
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); } }
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); } }
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); } }
Method Reference Summary Table
| Type | Syntax | Lambda Equivalent | Example |
|---|---|---|---|
| Static | ClassName::staticMethod | x -> ClassName.method(x) | Integer::parseInt |
| Bound Instance | instance::method | x -> instance.method(x) | System.out::println |
| Constructor | ClassName::new | () -> new ClassName() | ArrayList::new |
| Arbitrary Instance | ClassName::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); } }
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)); } }
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); } }
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())); } }
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")); } }
java.util.function — Master Reference Table
| Interface | Method Signature | Purpose | Real Use Case |
|---|---|---|---|
Predicate<T> | boolean test(T t) | Test a condition | Filter veg items from menu |
Function<T,R> | R apply(T t) | Transform T to R | Convert price to string "₹299" |
Consumer<T> | void accept(T t) | Consume/process | Print each order to console |
Supplier<T> | T get() | Generate/supply | Generate unique order IDs |
BiFunction<T,U,R> | R apply(T t, U u) | Two-arg transform | Calculate total = price × qty |
@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.
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.
Learn by Doing — 3-Tier Lab Structure
🟢 Tier 1 — GUIDED TASK: Refactor Zomato Restaurant Sorter from Anonymous Classes to Lambdas
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
Your Mission:
Build an order processing pipeline for Swiggy using Predicate, Function, Consumer, and Supplier.
Requirements:
- Create an
Orderclass with fields: orderId, customerName, items (List<String>), totalAmount, isPrepaid - Use a
Supplier<String>to auto-generate order IDs (format: "SWG-XXXXX") - Use a
Predicate<Order>to filter orders above ₹500 (eligible for free delivery) - Use a
Function<Order, Double>to calculate delivery fee (₹0 if above ₹500, else distance × ₹3) - Use a
Consumer<Order>to print order summary - 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 */);
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
The Brief:
Design and implement a complete food delivery pricing engine using lambdas, method references, and java.util.function interfaces. Your system should:
- Dynamic Pricing: Use
Function<Order, Double>for base price, surge pricing (based on time of day), and distance-based delivery fee - Discount Engine: Use
Predicate<Order>to check eligibility,Function<Double, Double>to apply discounts. Support stacking multiple discounts usingandThen() - Tax Calculator: Use
BiFunction<Double, String, Double>to calculate GST based on state (5% for food, 18% for packaged items) - Order Summary: Use
Consumer<Order>to print a formatted bill - Order Factory: Use
Supplier<Order>to generate test orders - 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.
Problem Set — Syntax, Programming, Industry & Interview
Syntax Questions (5)
- 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(); } }; - Beginner What is the correct lambda syntax for a
Runnablethat prints "Hello Lambda"? Why are empty parentheses()required? - Intermediate Convert this lambda to a method reference:
list.forEach(s -> System.out.println(s)); - Intermediate Write a
Predicate<String>lambda that returns true if a string has more than 5 characters AND starts with "A". Use theand()method. - 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 isx -> x + 1, output should bex -> x + 2.
Programming Questions (8)
- Beginner Write a Java program that sorts a list of Indian city names by length using a lambda expression with
Comparator. - Beginner Create a custom functional interface
Greetingwith methodString greet(String name). Implement it using a lambda that returns "Namaste, " + name + "!". - Intermediate Write a program using
Predicate<Integer>that filters even numbers, then usesFunction<Integer, Integer>to square them, andConsumer<Integer>to print results. - 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. - Intermediate Write a program that uses all 4 types of method references (static, bound instance, constructor, arbitrary instance) in a single class.
- Advanced Build a
Function<List<Integer>, Map<String, List<Integer>>>that categorises numbers into "Even" and "Odd" groups using lambdas and streams. - Advanced Create a mini calculator using
BiFunction<Double, Double, Double>stored in aMap<String, BiFunction>for operations: add, subtract, multiply, divide. - Advanced Write a chain of
Functions usingandThen()andcompose()that: (1) trims a string, (2) converts to uppercase, (3) appends " — PROCESSED". Apply to a list of messy inputs.
Industry Questions (3)
- 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.
- Advanced Flipkart's product recommendation engine filters products by category, price range, and rating. Design this using
Predicate<Product>withand(),or(), andnegate()chaining. - 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)
- Intermediate TCS/Infosys: "What is the difference between a lambda expression and an anonymous class? Can you always replace one with the other?"
- Advanced Flipkart/Amazon: "Explain the concept of 'effectively final' in the context of lambda expressions. Why is this restriction necessary?"
- Advanced Google/Microsoft: "What happens at the bytecode level when Java compiles a lambda expression? How is it different from an anonymous class?"
MCQ Assessment Bank — 30 Questions (Bloom's Mapped)
Remember / Identify (Q1–Q5)
A functional interface in Java must have exactly:
- Zero abstract methods
- One abstract method
- Two abstract methods
- Any number of abstract methods
Which annotation is used to explicitly declare a functional interface?
- @Lambda
- @FunctionalInterface
- @SingleMethod
- @Interface
The arrow operator in a lambda expression is:
- =>
- ->
- >>
- ::
Which of the following is NOT a type of method reference in Java?
- Static method reference
- Constructor reference
- Abstract method reference
- Arbitrary instance method reference
A static nested class can access which members of its outer class?
- Both static and instance members
- Only static members (including private static)
- Only public members
- No members of the outer class
Understand / Explain (Q6–Q10)
Why can a lambda expression only be used with a functional interface?
- Because lambdas can only have one parameter
- Because the compiler needs exactly one abstract method to determine what the lambda implements
- Because functional interfaces are faster than regular interfaces
- Because lambdas cannot implement default methods
What does "effectively final" mean in the context of lambda expressions?
- The variable is declared with the final keyword
- The variable's value is never changed after initialisation, even without the final keyword
- The variable is a constant defined at class level
- The variable is passed as a parameter to the lambda
Why does a non-static inner class hold an implicit reference to its outer class?
- To allow the inner class to be serialised
- To enable the inner class to access instance members of the outer class
- To make the inner class thread-safe
- To improve performance of method calls
What is the primary advantage of method references over lambda expressions?
- They execute faster at runtime
- They can do things lambdas cannot
- They improve readability when the lambda simply calls an existing method
- They support multiple method calls
System.out::println is clearer than x -> System.out.println(x).Which java.util.function interface would you use to check if an order amount is above ₹500?
- Function<Order, Boolean>
- Predicate<Order>
- Consumer<Order>
- Supplier<Boolean>
Apply / Implement (Q11–Q15)
What is the output of: Function<Integer, Integer> f = x -> x * 2; System.out.println(f.apply(5));
- 5
- 10
- Compilation error
- 25
Which lambda correctly implements BiFunction<String, String, Integer>?
- (a, b) -> a + b
- (a, b) -> a.length() + b.length()
- a -> a.length()
- (a, b, c) -> a.length()
What is the method reference equivalent of: list.forEach(s -> s.toUpperCase())?
- list.forEach(String::toUpperCase)
- list.forEach(s::toUpperCase)
- list.forEach(String.toUpperCase)
- This lambda cannot be converted to a method reference because forEach expects a Consumer, but toUpperCase returns a String
What does this code print? Predicate<String> p = s -> s.length() > 3; System.out.println(p.negate().test("Hi"));
- true
- false
- Compilation error
- Runtime exception
What is the correct way to create a Thread using a lambda?
- Thread t = () -> System.out.println("Run");
- Thread t = new Thread(() -> System.out.println("Run"));
- Thread t = new Thread(-> System.out.println("Run"));
- Thread t = new Runnable(() -> System.out.println("Run"));
Analyze / Compare (Q16–Q20)
When should you use a static nested class instead of a non-static inner class?
- When the nested class needs to access instance variables of the outer class
- When the nested class doesn't need a reference to an outer class instance and is a logically grouped utility
- When you need to create multiple instances of the nested class
- When the nested class needs to be serialised
What is the key difference between Function<T, R> and Consumer<T>?
- Function takes no parameters, Consumer takes one
- Function returns a value (R), Consumer returns void
- Consumer can be chained, Function cannot
- Function is for primitive types only
Why might an anonymous class be preferred over a lambda expression?
- When you need to implement an interface with multiple abstract methods
- When you need better performance
- When the code needs to run on Java 7
- Both A and C
In String::compareToIgnoreCase, what type of method reference is this?
- Static method reference
- Bound instance method reference
- Constructor reference
- Arbitrary instance method reference
What is the difference between f.andThen(g) and f.compose(g) for Function f and g?
- No difference — they are the same
- andThen applies f first then g; compose applies g first then f
- andThen is for Function, compose is for Consumer
- andThen chains sequentially, compose chains in parallel
Evaluate / Justify (Q21–Q25)
A developer writes: int x = 10; Runnable r = () -> System.out.println(x); x = 20; — What happens?
- Prints 10
- Prints 20
- Compilation error — x is not effectively final
- Runtime exception
Which approach is most appropriate for a one-time Comparator in a sort call?
- Define a separate class implementing Comparator
- Use an anonymous class
- Use a lambda expression
- Use a local class inside the method
A team is debating whether to replace ALL anonymous classes with lambdas during a codebase refactoring. What is the correct evaluation?
- Replace all — lambdas are always better
- Replace only those implementing functional interfaces — anonymous classes extending abstract classes or implementing multi-method interfaces cannot be replaced
- Don't replace any — anonymous classes are more readable
- Replace only if the project uses Java 11+
A developer uses Predicate<String> p = s -> { return s != null && s.length() > 0; }; — How can this be improved?
- Use expression lambda: s -> s != null && s.length() > 0
- Use method reference: String::isEmpty
- Use Predicate.not(String::isEmpty) combined with Objects::nonNull
- Both A and C are valid improvements; C is most idiomatic
Is it valid to define an interface with one abstract method and three default methods, and annotate it with @FunctionalInterface?
- No — default methods count as abstract methods
- Yes — only abstract methods count; default methods are excluded
- No — @FunctionalInterface requires zero default methods
- Yes — but only if the default methods are private
Create / Design (Q26–Q30)
You need to design a data pipeline: filter orders → calculate tax → format receipt. Which chain of functional interfaces is correct?
- Consumer → Function → Supplier
- Predicate → Function → Consumer
- Supplier → Predicate → Function
- Function → Consumer → Predicate
To create a flexible discount system where different discount strategies can be plugged in at runtime, which design is most appropriate?
- Use inheritance with abstract DiscountCalculator class
- Use a Function<Double, Double> parameter that accepts different lambda implementations
- Use if-else statements for each discount type
- Use enum with switch-case
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?
- Write three separate if statements
- Create one Predicate with all logic in a single lambda body
- Create three Predicates and chain with .and(): notNull.and(notEmpty).and(matchesPattern)
- Use a for loop over a list of validation rules
You need a factory method that creates different types of notifications (SMS, Email, Push). Which functional interface is best suited?
- Predicate<String>
- Consumer<String>
- Function<String, Notification>
- Supplier<Notification>
Design a logging decorator using Function composition. Given Function<Request, Response> handler, how would you add logging before and after?
- Subclass the handler and override apply()
- Wrap with a new Function that logs, calls handler.apply(), logs again, and returns the result
- Use handler.andThen(logFunction) only
- Use AOP (Aspect-Oriented Programming) exclusively
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.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:
| Aspect | Lambda Expression | Anonymous Class |
|---|---|---|
| Type | Can only implement functional interfaces (1 abstract method) | Can implement any interface or extend abstract classes |
this keyword | Refers to the enclosing class | Refers to the anonymous class itself |
| Compilation | Uses 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:
| Feature | Static Nested Class | Non-static Inner Class |
|---|---|---|
| Outer instance reference | No implicit reference to outer class | Holds implicit reference to outer instance |
| Access to outer members | Only static members (incl. private static) | All members (static + instance, incl. private) |
| Creation syntax | new Outer.Nested() | outerObj.new Inner() |
| Memory | Lightweight — no outer reference | Heavier — prevents GC of outer class |
| Common use | Builder pattern, helpers | Iterators, 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.
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")); } }
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.
Lab Programs
No specific university lab program is mapped to Unit 10. Lab 2 — Multithreading via Lambda is covered in Unit 11.
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).
| Detail | Info |
|---|---|
| Tools Used Daily | Java 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 Devs | Swiggy, Flipkart, Zomato, Razorpay, PhonePe, Paytm, Amazon, Google, Goldman Sachs, Morgan Stanley, TCS Digital, Infosys Power Programmer |
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
| Platform | Best For | Typical Rate |
|---|---|---|
| Internshala | Student projects, Java assignments | ₹2,000–₹8,000/project |
| Fiverr | Java code review & refactoring gigs | $20–$100/gig (₹1,600–₹8,000) |
| Upwork | Enterprise legacy Java modernisation | $25–$60/hour |
| Direct outreach to Indian IT companies | ₹10,000–₹30,000/project | |
| Toptal | Premium 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).
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;
@FunctionalInterfaceenforces this at compile time - Lambda expressions provide concise syntax:
(params) -> expressionor(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
Checkpoint — Self-Assessment
| Skill / Concept | Tool / Technique | Portfolio Piece | Earn-Ready? |
|---|---|---|---|
| Nested Classes (4 types) | Java compiler, IntelliJ | — | ✅ Yes — conceptual interview prep |
| Lambda Expressions | Java 8+, any IDE | Refactored Zomato Sorter | ✅ Yes — essential for any Java job |
| Functional Interfaces | @FunctionalInterface | Custom DiscountPolicy interface | ✅ Yes — design skill for APIs |
| Method References | :: operator, 4 types | All-types demo program | ✅ Yes — code review skill |
| java.util.function | Predicate, Function, Consumer, Supplier, BiFunction | Swiggy Order Pipeline | ✅ Yes — ₹3,000–₹10,000/project |
| Functional Composition | andThen, compose, and, or | Pricing Engine | ✅ Yes — advanced Java gigs |
| Legacy Refactoring | Anonymous → Lambda conversion | Before/After showcase on GitHub | ✅ Yes — ₹5,000–₹20,000/project |
✅ Unit 10 complete. MCQs: 30. Ready for Unit 11!
[QR: Link to EduArtha video tutorial — Nested Classes & Lambda Expressions]