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; }.
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
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).
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.
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.
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); } }