Detailed Explanation of Lambda Expressions and Functional Programming in Java

Detailed Explanation of Lambda Expressions and Functional Programming in Java

I. Overview of Lambda Expressions
Lambda expressions are a core feature introduced in Java 8, essentially representing an anonymous function (method). They allow functions to be passed as method parameters, simplifying code writing. Their emergence addressed the issue of code redundancy in anonymous inner classes.

II. Syntax Structure of Lambda Expressions
Full syntax format: (parameter list) -> { code block }

  • Parameter list: Matches the interface method parameters; types can be omitted (type inference).
  • ->: Lambda operator, separating parameters and code.
  • Code block: Specific implementation logic; {} and ; can be omitted for single-line code.

III. Evolution Steps of Lambda Expressions

  1. Traditional anonymous inner class approach:
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};
  1. Lambda simplification process:
  • Step 1: Retain core parameters and method body
    Runnable r = () -> { System.out.println("Hello"); };
    
  • Step 2: Omit {} and ; for single-line code
    Runnable r = () -> System.out.println("Hello");
    

IV. Functional Interfaces
Lambda expressions must rely on functional interfaces, which is key to understanding their nature:

  1. Definition: An interface containing only one abstract method (may include default/static methods).
  2. @FunctionalInterface annotation: Compiler verifies if the interface meets the specification.
  3. Common built-in functional interfaces:
    • Consumer<T>: Consumer type, accepts parameters with no return value.
    • Supplier<T>: Supplier type, no parameters but returns a result.
    • Function<T,R>: Function type, accepts parameters and returns a result.
    • Predicate<T>: Predicate type, accepts parameters and returns a boolean.

V. Method References
Syntactic sugar for lambda expressions, further simplifying code:

  1. Static method reference: ClassName::staticMethod
    list.forEach(System.out::println);
    
  2. Instance method reference: instance::method
    String str = "test";
    Supplier<Integer> s = str::length;
    
  3. Constructor reference: ClassName::new
    Supplier<List<String>> supplier = ArrayList::new;
    

VI. Implementation Principle of Lambda Expressions

  1. Compilation phase: The compiler generates private static methods to store the lambda body code.
  2. Runtime phase: Uses the invokedynamic instruction for dynamic method binding.
  3. Advantage: Avoids generating additional class files like anonymous inner classes, improving performance.

VII. Practical Application Scenarios

  1. Collection iteration:
    list.forEach(item -> System.out.println(item));
    
  2. Thread creation:
    new Thread(() -> System.out.println("Thread executing")).start();
    
  3. Conditional filtering:
    list.stream().filter(s -> s.length() > 3).collect(Collectors.toList());
    

VIII. Considerations

  1. Variable capture: Lambda can only reference final or effectively final local variables.
  2. this reference: The this inside a lambda refers to the enclosing class, unlike in inner classes.
  3. Performance considerations: Using lambda expressions in frequently called hot code can yield better performance improvements.