Lambda Expressions & Streams
Dashboard
Topic 19/30

Lambda Expressions & Streams

Java 8 introduced Functional Programming concepts, fundamentally changing how Java code is written. Lambda expressions provide a concise way to implement single-method interfaces, while the Stream API offers declarative data processing.

Lambda Expressions Syntax

A lambda expression is an anonymous block of code. Its basic syntax is (parameters) -> expression or (parameters) -> { statements; }.

LambdaBasicsDemo.javaJava
import java.util.ArrayList;
import java.util.List;

public class LambdaBasicsDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");

        // Using lambda to iterate and print
        names.forEach(name -> System.out.println("Hello, " + name));
        
        // Lambda for a custom Functional Interface
        MathOperation add = (a, b) -> a + b;
        System.out.println("Sum: " + add.operate(10, 5));
    }
}

@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

Functional Interfaces

An interface with exactly one abstract method is called a Functional Interface. Java provides built-in functional interfaces in the java.util.function package:

  • Predicate<T>: Takes T, returns boolean. Used for filtering.
  • Function<T, R>: Takes T, returns R. Used for transforming data.
  • Consumer<T>: Takes T, returns nothing. Used for side-effects (e.g., printing).
  • Supplier<T>: Takes nothing, returns T. Used for lazy generation.

Method References

Method references are a shorthand syntax for a lambda expression that contains just one method call. Syntax: ClassName::methodName

MethodReferenceDemo.javaJava
import java.util.Arrays;
import java.util.List;

public class MethodReferenceDemo {
    public static void main(String[] args) {
        List<String> messages = Arrays.asList("Java", "is", "great");

        // Instead of: msg -> System.out.println(msg)
        messages.forEach(System.out::println); // Instance method reference
    }
}

Stream API

The Stream API allows declarative data processing. A Stream is a sequence of elements supporting sequential and parallel aggregate operations.

  • Intermediate Operations: Return a new stream and are lazy (e.g., filter, map, sorted, limit).
  • Terminal Operations: Produce a result or side-effect and trigger processing (e.g., collect, forEach, reduce, count).
StreamFilterMapDemo.javaJava
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Employee {
    String name;
    double salary;
    public Employee(String name, double salary) { this.name = name; this.salary = salary; }
    public double getSalary() { return salary; }
    public String getName() { return name; }
}

public class StreamFilterMapDemo {
    public static void main(String[] args) {
        List<Employee> emps = Arrays.asList(
            new Employee("Alice", 50000),
            new Employee("Bob", 80000),
            new Employee("Charlie", 60000)
        );

        // Filter employees with salary > 55000 and extract their names
        List<String> highEarners = emps.stream()
            .filter(e -> e.getSalary() > 55000)
            .map(Employee::getName)
            .collect(Collectors.toList());

        System.out.println("High Earners: " + highEarners);
    }
}

Collectors

The Collectors class provides implementations for common reduction operations like grouping, joining, and mapping.

CollectorsDemo.javaJava
import java.util.*;
import java.util.stream.Collectors;

class Worker {
    String name, dept;
    public Worker(String name, String dept) { this.name = name; this.dept = dept; }
    public String getDept() { return dept; }
    public String getName() { return name; }
    @Override public String toString() { return name; }
}

public class CollectorsDemo {
    public static void main(String[] args) {
        List<Worker> workers = Arrays.asList(
            new Worker("John", "IT"), new Worker("Jane", "HR"), new Worker("Doe", "IT")
        );

        // Grouping by Department
        Map<String, List<Worker>> byDept = workers.stream()
            .collect(Collectors.groupingBy(Worker::getDept));
            
        System.out.println("Grouped: " + byDept);
        
        // Joining Names
        String names = workers.stream().map(Worker::getName).collect(Collectors.joining(", "));
        System.out.println("All names: " + names);
    }
}

Optional Class

Optional is a container object used to contain not-null objects. It's designed to provide a better alternative to returning null and avoiding NullPointerException.

OptionalDemo.javaJava
import java.util.Optional;
import java.util.Arrays;
import java.util.List;

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

        // findFirst returns an Optional
        Optional<String> bName = names.stream()
            .filter(n -> n.startsWith("B"))
            .findFirst();

        // Safe retrieval
        bName.ifPresent(name -> System.out.println("Found: " + name));
        
        // Default value if not found
        String zName = names.stream()
            .filter(n -> n.startsWith("Z"))
            .findFirst()
            .orElse("Not Found");
            
        System.out.println("Z name: " + zName);
    }
}