Lambda Expressions

Introduction

Lambda expressions (Java 8+) provide a concise way to write anonymous functions. Enable functional programming in Java.


Basic Syntax

(parameters) -> expression

// Or with block
(parameters) -> {
    statements;
    return value;
}

Simple Examples

// No parameters
() -> System.out.println("Hello");

// One parameter (parentheses optional)
x -> x * x;
(x) -> x * x;  // Same as above

// Multiple parameters
(x, y) -> x + y;

// With block
(x, y) -> {
    int sum = x + y;
    return sum;
};

Before Lambda (Anonymous Inner Class)

// Old way - anonymous inner class
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello World");
    }
};

r.run();

After Lambda (Concise)

// New way - lambda expression
Runnable r = () -> System.out.println("Hello World");

r.run();

Functional Interface

Interface with exactly one abstract method.

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);  // Single abstract method
}

public class Main {
    public static void main(String[] args) {
        // Lambda for addition
        Calculator add = (a, b) -> a + b;
        System.out.println(add.calculate(10, 5));  // 15

        // Lambda for multiplication
        Calculator multiply = (a, b) -> a * b;
        System.out.println(multiply.calculate(10, 5));  // 50

        // Lambda with block
        Calculator subtract = (a, b) -> {
            int result = a - b;
            return result;
        };
        System.out.println(subtract.calculate(10, 5));  // 5
    }
}

Lambda with Collections

forEach():

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Alice", "Bob");

        // Lambda with forEach
        names.forEach(name -> System.out.println(name));

        // Or method reference
        names.forEach(System.out::println);
    }
}

Comparator:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

        // Lambda for sorting
        Collections.sort(numbers, (a, b) -> a - b);
        System.out.println(numbers);  // [1, 2, 5, 8, 9]

        // Descending
        Collections.sort(numbers, (a, b) -> b - a);
        System.out.println(numbers);  // [9, 8, 5, 2, 1]
    }
}

Lambda with Threads

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

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

        t1.start();
        t2.start();
    }
}

Built-in Functional Interfaces

1. Predicate:

Test a condition (returns boolean)

import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        // Check if number is even
        Predicate<Integer> isEven = n -> n % 2 == 0;

        System.out.println(isEven.test(4));  // true
        System.out.println(isEven.test(5));  // false

        // Check if string is long
        Predicate<String> isLong = s -> s.length() > 5;
        System.out.println(isLong.test("Hello"));  // false
        System.out.println(isLong.test("HelloWorld"));  // true
    }
}

2. Function<T, R>:

Transform input to output

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        // Convert String to Integer
        Function<String, Integer> stringLength = s -> s.length();

        System.out.println(stringLength.apply("Hello"));  // 5

        // Square a number
        Function<Integer, Integer> square = n -> n * n;
        System.out.println(square.apply(5));  // 25
    }
}

3. Consumer:

Consume (use) input, no return

import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        // Print with prefix
        Consumer<String> print = s -> System.out.println("Value: " + s);

        print.accept("Hello");  // Value: Hello
        print.accept("World");  // Value: World
    }
}

4. Supplier:

Supply (provide) output, no input

import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        // Supply random number
        Supplier<Double> randomValue = () -> Math.random();

        System.out.println(randomValue.get());
        System.out.println(randomValue.get());
    }
}

Stream API with Lambda

filter():

import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Filter even numbers
        List<Integer> evens = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());

        System.out.println(evens);  // [2, 4, 6, 8, 10]
    }
}

map():

import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Square each number
        List<Integer> squares = numbers.stream()
            .map(n -> n * n)
            .collect(Collectors.toList());

        System.out.println(squares);  // [1, 4, 9, 16, 25]
    }
}

reduce():

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Sum all numbers
        int sum = numbers.stream()
            .reduce(0, (a, b) -> a + b);

        System.out.println(sum);  // 15
    }
}

Complete Example: Student Processing

import java.util.*;
import java.util.stream.*;

class Student {
    String name;
    int marks;

    Student(String name, int marks) {
        this.name = name;
        this.marks = marks;
    }

    @Override
    public String toString() {
        return name + " (" + marks + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("John", 85),
            new Student("Alice", 92),
            new Student("Bob", 78),
            new Student("Carol", 88),
            new Student("David", 65)
        );

        // Filter students with marks > 80
        System.out.println("Students with marks > 80:");
        students.stream()
            .filter(s -> s.marks > 80)
            .forEach(System.out::println);

        // Get names of top students
        System.out.println("\nTop student names:");
        List<String> topNames = students.stream()
            .filter(s -> s.marks > 80)
            .map(s -> s.name)
            .collect(Collectors.toList());
        System.out.println(topNames);

        // Count students with marks > 80
        long count = students.stream()
            .filter(s -> s.marks > 80)
            .count();
        System.out.println("\nCount: " + count);

        // Average marks
        double average = students.stream()
            .mapToInt(s -> s.marks)
            .average()
            .orElse(0.0);
        System.out.println("Average: " + average);

        // Sort by marks (descending)
        System.out.println("\nSorted by marks (desc):");
        students.stream()
            .sorted((s1, s2) -> s2.marks - s1.marks)
            .forEach(System.out::println);
    }
}

Method References

Shorthand for lambda expressions.

Types:

  1. Static method: ClassName::staticMethod
  2. Instance method: object::instanceMethod
  3. Constructor: ClassName::new
  4. Instance method of type: ClassName::instanceMethod
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Alice", "Bob");

        // Lambda
        names.forEach(name -> System.out.println(name));

        // Method reference (equivalent)
        names.forEach(System.out::println);

        // Constructor reference
        List<Integer> numbers = Arrays.asList(1, 2, 3);
        List<String> strings = numbers.stream()
            .map(String::valueOf)  // Integer to String
            .collect(Collectors.toList());

        System.out.println(strings);  // [1, 2, 3]
    }
}

Lambda Variable Capture

Lambda can access variables from enclosing scope (must be final or effectively final).

public class Main {
    public static void main(String[] args) {
        int multiplier = 10;  // Effectively final

        // Lambda captures multiplier
        Function<Integer, Integer> multiply = n -> n * multiplier;

        System.out.println(multiply.apply(5));  // 50

        // multiplier = 20;  // Error: cannot modify captured variable
    }
}

Real-World Example: Calculator

import java.util.*;
import java.util.function.BiFunction;

public class Calculator {
    // Store operations as lambdas
    private Map<String, BiFunction<Integer, Integer, Integer>> operations;

    public Calculator() {
        operations = new HashMap<>();

        // Register operations using lambdas
        operations.put("add", (a, b) -> a + b);
        operations.put("subtract", (a, b) -> a - b);
        operations.put("multiply", (a, b) -> a * b);
        operations.put("divide", (a, b) -> a / b);
        operations.put("power", (a, b) -> (int) Math.pow(a, b));
        operations.put("mod", (a, b) -> a % b);
    }

    public int calculate(String operation, int a, int b) {
        BiFunction<Integer, Integer, Integer> op = operations.get(operation);
        if (op == null) {
            throw new IllegalArgumentException("Unknown operation: " + operation);
        }
        return op.apply(a, b);
    }

    public static void main(String[] args) {
        Calculator calc = new Calculator();

        System.out.println("10 + 5 = " + calc.calculate("add", 10, 5));
        System.out.println("10 - 5 = " + calc.calculate("subtract", 10, 5));
        System.out.println("10 * 5 = " + calc.calculate("multiply", 10, 5));
        System.out.println("10 / 5 = " + calc.calculate("divide", 10, 5));
        System.out.println("2 ^ 5 = " + calc.calculate("power", 2, 5));
        System.out.println("10 % 3 = " + calc.calculate("mod", 10, 3));
    }
}

Lambda vs Anonymous Class

FeatureLambdaAnonymous Class
SyntaxConciseVerbose
thisEnclosing classAnonymous class
VariablesEffectively finalNo restriction
Functional interfaceYesAny interface
PerformanceBetterSlower
ScopeEnclosing scopeOwn scope

Common Functional Interfaces

InterfaceMethodUse
Predicateboolean test(T t)Test condition
Function<T,R>R apply(T t)Transform
Consumervoid accept(T t)Consume
SupplierT get()Supply
BiFunction<T,U,R>R apply(T t, U u)Two inputs
UnaryOperatorT apply(T t)Same type I/O
BinaryOperatorT apply(T t1, T t2)Two same type

Quick Reference

// Basic syntax
(parameters) -> expression
(parameters) -> { statements; }

// No parameters
() -> System.out.println("Hello");

// One parameter
x -> x * x;

// Multiple parameters
(x, y) -> x + y;

// With type
(Integer x, Integer y) -> x + y;

// Functional interface
@FunctionalInterface
interface MyInterface {
    void method();
}

MyInterface lambda = () -> System.out.println("Hello");

// Built-in interfaces
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> length = s -> s.length();
Consumer<String> print = s -> System.out.println(s);
Supplier<Double> random = () -> Math.random();

// Method reference
names.forEach(System.out::println);

Exam Tips

Remember:

  1. Lambda = anonymous function
  2. Syntax: (params) -> expression
  3. Functional interface required
  4. One abstract method only
  5. @FunctionalInterface annotation
  6. Predicate for testing
  7. Function for transformation
  8. Consumer for consuming
  9. Supplier for supplying
  10. Method reference shorthand

Common Questions:

  • What are lambda expressions?
  • Lambda syntax?
  • What is functional interface?
  • Built-in functional interfaces?
  • Lambda vs anonymous class?
  • Method reference types?
  • Variable capture rules?
  • Lambda with Streams?
  • Benefits of lambda?
  • When introduced in Java?