☕
Core Java Roadmap Phase 07
0 / 40 (0%)
☰ Syllabus Index Next: P08 →

Java 8 LTS • Functional Interfaces, Lambdas, Method References & Streams

📂 Repository Module: Module 2_ Java LTS Features \ 01_Java8_Demos
📖 Textbook Alignment: Volume I — Chapter 6: Interfaces and Lambda Expressions • Volume II — Chapter 1: Streams
Phase 7

Java 8 LTS • Functional Interfaces, Lambdas, Method References & Streams

📂 Module 2_ Java LTS Features \ 01_Java8_Demos
📖 Core Java Textbook Alignment: Cay S. Horstmann • Volume I — Chapter 6: Interfaces, Lambda Expressions • Volume II — Chapter 1: Streams

7.1 Single Abstract Method (SAM) Interfaces, Lambdas & Method References

📖 Cay S. Horstmann • Core Java Volume I — Chapter 6, Section 6.2 ("Lambda Expressions")
“A lambda expression is a block of code with parameters that can be passed to other methods. In Java, you can pass code around by using lambda expressions with functional interfaces—interfaces with a single abstract method. The Java compiler translates a lambda expression into a private method inside the containing class and invokes it using the invokedynamic bytecode instruction, avoiding the class generation overhead of anonymous inner classes.”
  • SAM Axiom Single Abstract Method Requirement: Any interface with exactly one abstract method is a Functional Interface (annotated with @FunctionalInterface). Default and static methods do not count toward this limit.
  • Method Reference Syntax Four Forms: Class::staticMethod, instance::instanceMethod, Class::instanceMethod, and Constructor References Class::new.
💡 Real-World Analogy: Passing Code as a Recipe vs Hiring a Chef
In Java 7, if you wanted to tell a method how to sort pizzas, you had to hire an entire temporary chef (create an Anonymous Inner Class with boilerplate new Comparator<Pizza>() { public int compare(...) {...} }).
In Java 8, you simply text the recipe directly: (p1, p2) -> p1.getPrice() - p2.getPrice()! You pass pure behavior as data.
⚡ Pro-Tip / Interview Memory Hook: Lambda vs Anonymous Inner Class Memory Secret
Anonymous Inner Classes: Generate a physical Outer$1.class file on disk and allocate a distinct object on the Heap every single time!
Lambdas: Do NOT generate separate class files. The HotSpot compiler uses the invokedynamic (INDY) bytecode opcode with LambdaMetafactory, generating lightweight function pointers that avoid heap allocation overhead!
📂 Module 2_ Java LTS Features/.../Tester1003NeedOfMethodReferenceConstructor.java
package com.accenture.ltt.mr.custom.interfaces;

class Customer {
    private String name;
    public Customer(String name) {
        this.name = name;
    }
    public String getName() { return name; }
}

@FunctionalInterface
interface CustomerFactory {
    Customer create(String name);
}

public class Tester1003NeedOfMethodReferenceConstructor {
    public static void main(String[] args) {
        // 1. Traditional Lambda syntax
        CustomerFactory f1 = name -> new Customer(name);
        
        // 2. Elegant Java 8 Constructor Method Reference: Class::new
        CustomerFactory f2 = Customer::new;
        
        Customer c = f2.create("Mohpreet Singh");
        System.out.println("Created customer: " + c.getName());
    }
}
👁 Execution & Memory Breakdown • Method Reference Mechanics
Line 15: Customer::new binds directly to the single-argument constructor Customer(String) matching the SAM signature of CustomerFactory.create(String).
Runtime: No intermediate adapter class is loaded. The JVM invokes the constructor directly through the invokedynamic call site.

7.2 Java 8 Streams API: Pipelines, Lazy Evaluation & Collectors

📖 Cay S. Horstmann • Core Java Volume II — Chapter 1, Section 1.1 ("From Iterating to Stream Operations")
“Streams differ from collections in three crucial ways: 1. A stream does not store its elements; they may be stored in an underlying collection or generated on demand. 2. Stream operations do not mutate their source; they return a new stream with the results. 3. Stream operations are lazily evaluated when possible. They are not carried out until an ultimate terminal operation requests the result.”
  • Lazy Pipeline Rule No Terminal = Zero Execution: Intermediate operations (filter(), map(), peek()) execute NOTHING until a terminal operation (collect(), count(), forEach()) pulls elements.
  • Short-Circuiting O(1) Optimization: Operations like findFirst(), anyMatch(), and limit(n) terminate the pipeline immediately without traversing remaining stream elements.
💡 Real-World Analogy: The Factory Assembly Line Conveyor Belt
Imagine an automated car manufacturing line:
• Intermediate Operations (filter, map): These are robotic arms stationed along the conveyor belt (e.g., "paint blue", "reject defective parts"). If the conveyor belt is turned off, the robots do nothing!
• Terminal Operation (collect): Turning on the master switch at the shipping warehouse. Parts are pulled down the conveyor belt one by one, transformed in-flight, and packed into shipping crates!
📂 Module 2_ Java LTS Features/.../Tester1001Filter.java & Tester1003Collect.java
package com.accenture.ltt.nonterminal;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamMasteryDemo {
    record Product(int id, String name, String category, double price) {}

    public static void main(String[] args) {
        List<Product> products = List.of(
            new Product(101, "Laptop", "Electronics", 1200.0),
            new Product(102, "Smartphone", "Electronics", 800.0),
            new Product(103, "Desk Chair", "Furniture", 150.0),
            new Product(104, "Coffee Maker", "Appliances", 90.0)
        );

        // 1. Filter + Map + Collect to List
        List<String> premiumElectronics = products.stream()
            .filter(p -> p.category().equals("Electronics"))
            .filter(p -> p.price() > 500.0)
            .map(Product::name)
            .collect(Collectors.toList());
        System.out.println("Premium Electronics: " + premiumElectronics);

        // 2. GroupingBy Category & Calculating Average Price
        Map<String, Double> avgPricePerCategory = products.stream()
            .collect(Collectors.groupingBy(
                Product::category,
                Collectors.averagingDouble(Product::price)
            ));
        System.out.println("Average Prices: " + avgPricePerCategory);
    }
}
👁 Execution & Memory Breakdown • Stream Lazy Evaluation
Step 1 (Spliterator Pipeline): products.stream() creates a Spliterator over the source array without copying elements into a new buffer.
Step 2 (Fusing Stages): The HotSpot JIT compiler fuses the two filter() predicates and the map() transformation into a single unified loop pass, eliminating intermediate heap collections.