Java Lambda Expressions

Master the art of writing clean, functional code with Java's powerful lambda expressions introduced in Java 8.

What is a Lambda Expression?

A lambda expression is a short, anonymous function that can be passed around as a parameter. It provides a clear and concise way to represent a method interface using an expression. Think of it as a shortcut to write code without creating a full method or class.

Lambda expressions enable functional programming in Java, making your code more readable and maintainable.

  • No need to write lengthy anonymous inner classes
  • Makes code shorter and more readable
  • Enables functional programming style in Java
  • Works with functional interfaces (interfaces with single abstract method)

Fun Fact: Lambda expressions were introduced in Java 8 (2014) and revolutionized the way Java developers write code, making it more similar to modern languages like Python and JavaScript!

Lambda Expression Syntax

The basic syntax of a lambda expression consists of three parts:

Lambda Syntax Structure

(parameters) -> expression

// OR

(parameters) -> { statements; }

Components:

  • Parameters: Input parameters (can be zero or more)
  • Arrow Token (->): Separates parameters from body
  • Body: Contains expressions or statements

Note: If there's only one parameter, you can omit parentheses. If there's only one statement, you can omit curly braces and the return keyword.

Before and After Lambda

Let's compare traditional approach vs lambda expression approach:

Example: Without Lambda (Traditional Way)

// Functional Interface
interface Greeting {
    void sayHello(String name);
}

public class Main {
    public static void main(String[] args) {
        // Traditional approach using anonymous inner class
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello(String name) {
                System.out.println("Hello, " + name + "!");
            }
        };
        
        greeting.sayHello("John");
    }
}
Output:
Hello, John!

Example: With Lambda (Modern Way)

// Functional Interface
interface Greeting {
    void sayHello(String name);
}

public class Main {
    public static void main(String[] args) {
        // Lambda expression - much cleaner!
        Greeting greeting = (name) -> System.out.println("Hello, " + name + "!");
        
        greeting.sayHello("John");
    }
}
Output:
Hello, John!

Comparison:

  • Traditional: 8 lines of code with boilerplate
  • Lambda: 1 line of clean, readable code
  • Same functionality: Both produce identical results

Types of Lambda Expressions

1. No Parameters

Lambda with no parameters uses empty parentheses.

Example: () -> System.out.println("Hello!");

2. Single Parameter

When there's only one parameter, parentheses are optional.

Example: name -> System.out.println(name); or (name) -> System.out.println(name);

3. Multiple Parameters

Multiple parameters must be enclosed in parentheses.

Example: (a, b) -> a + b;

4. With Return Statement

For multiple statements, use curly braces and explicit return.

Example: (a, b) -> { int sum = a + b; return sum; }

Lambda with Different Scenarios

Here are various examples demonstrating different lambda use cases:

Example: Lambda Variations

// 1. No parameters
interface Message {
    void display();
}

// 2. Single parameter
interface Square {
    int calculate(int x);
}

// 3. Multiple parameters
interface Sum {
    int add(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        // 1. Lambda with no parameters
        Message msg = () -> System.out.println("Welcome to Lambda!");
        msg.display();
        
        // 2. Lambda with single parameter (no parentheses)
        Square square = x -> x * x;
        System.out.println("Square of 5: " + square.calculate(5));
        
        // 3. Lambda with multiple parameters
        Sum sum = (a, b) -> a + b;
        System.out.println("Sum: " + sum.add(10, 20));
        
        // 4. Lambda with multiple statements
        Sum complex = (a, b) -> {
            int result = a + b;
            System.out.println("Calculating sum...");
            return result;
        };
        System.out.println("Complex Sum: " + complex.add(15, 25));
    }
}
Output:
Welcome to Lambda!
Square of 5: 25
Sum: 30
Calculating sum...
Complex Sum: 40

Explanation:

  • No parameters: Uses empty parentheses () before arrow
  • Single parameter: Parentheses optional, direct parameter name
  • Multiple parameters: Comma-separated parameters in parentheses
  • Multiple statements: Requires curly braces and explicit return

Lambda with Collections

Lambda expressions are extremely useful when working with Java Collections and Streams:

Example: Lambda with ArrayList

import java.util.ArrayList;
import java.util.Collections;

public class Main {
    public static void main(String[] args) {
        ArrayList fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Mango");
        fruits.add("Banana");
        fruits.add("Orange");
        
        System.out.println("Original List: " + fruits);
        
        // Lambda with forEach - Print each element
        System.out.println("\nUsing forEach with Lambda:");
        fruits.forEach(fruit -> System.out.println("- " + fruit));
        
        // Lambda with sort
        Collections.sort(fruits, (f1, f2) -> f1.compareTo(f2));
        System.out.println("\nSorted List: " + fruits);
        
        // Lambda with removeIf
        fruits.removeIf(fruit -> fruit.startsWith("M"));
        System.out.println("\nAfter removing fruits starting with 'M': " + fruits);
    }
}
Output:
Original List: [Apple, Mango, Banana, Orange]

Using forEach with Lambda:
- Apple
- Mango
- Banana
- Orange

Sorted List: [Apple, Banana, Mango, Orange]

After removing fruits starting with 'M': [Apple, Banana, Orange]

Explanation:

  • forEach(): Iterates through each element using lambda
  • sort(): Custom sorting logic using lambda comparator
  • removeIf(): Removes elements based on lambda condition
  • Concise code: All operations done in single lines

Lambda with Threads

Lambda expressions make creating threads much simpler:

Example: Thread Creation with Lambda

public class Main {
    public static void main(String[] args) {
        // Traditional way
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread 1: Traditional approach");
            }
        });
        
        // Lambda way - Much cleaner!
        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2: Lambda approach");
        });
        
        // Even shorter lambda
        Thread thread3 = new Thread(() -> System.out.println("Thread 3: One-line lambda"));
        
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
Output:
Thread 1: Traditional approach
Thread 2: Lambda approach
Thread 3: One-line lambda

Explanation:

  • Runnable interface: Has single abstract method run(), perfect for lambda
  • Traditional vs Lambda: 6 lines reduced to 1 line
  • Readability: Lambda makes thread creation much clearer

Built-in Functional Interfaces

Java provides several built-in functional interfaces in the java.util.function package:

Example: Using Predicate and Function

import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        // Predicate: Takes input, returns boolean
        Predicate isEven = num -> num % 2 == 0;
        System.out.println("Is 10 even? " + isEven.test(10));
        System.out.println("Is 7 even? " + isEven.test(7));
        
        // Function: Takes input, returns output
        Function stringLength = str -> str.length();
        System.out.println("\nLength of 'Lambda': " + stringLength.apply("Lambda"));
        
        // Consumer: Takes input, returns nothing
        Consumer printUpperCase = str -> System.out.println(str.toUpperCase());
        System.out.print("\nUpperCase: ");
        printUpperCase.accept("hello world");
    }
}
Output:
Is 10 even? true
Is 7 even? false

Length of 'Lambda': 6

UpperCase: HELLO WORLD

Common Functional Interfaces:

  • Predicate<T>: Takes T, returns boolean (for conditions/filters)
  • Function<T,R>: Takes T, returns R (for transformations)
  • Consumer<T>: Takes T, returns nothing (for actions)
  • Supplier<T>: Takes nothing, returns T (for generating values)

Advantages of Lambda Expressions

  • Concise Code: Reduces boilerplate code significantly
  • Readable: Makes code more clear and easier to understand
  • Functional Programming: Enables functional programming paradigm in Java
  • Better Performance: Can improve performance in some scenarios with parallel processing
  • Easy Maintenance: Less code means fewer bugs and easier maintenance
  • Stream API: Works seamlessly with Java 8 Stream API for data processing

💡 When to Use Lambda: Use lambda expressions when you need to pass simple behavior as a parameter, especially with functional interfaces. Avoid using them for complex logic that would be better suited in a named method.

Key Points to Remember

  • Lambda expressions work only with functional interfaces (interfaces with single abstract method)
  • Parentheses are optional for single parameter, mandatory for zero or multiple parameters
  • Curly braces are optional for single expression, mandatory for multiple statements
  • Return keyword is optional for single expression, mandatory with curly braces
  • Lambda can access effectively final local variables from enclosing scope
  • Use @FunctionalInterface annotation to mark functional interfaces (optional but recommended)

Best Practice: Keep lambda expressions short and simple. If your lambda becomes too complex (more than 3-4 lines), consider extracting it into a separate method for better readability.