Lambda Expressions in Java

Lambda expressions are a fundamental feature introduced in Java 8 that enable functional programming and simplify the implementation of interfaces with a single abstract method (functional interfaces).

1. Basic Syntax

// Traditional way using anonymous class Runnable traditionalRunnable = new Runnable() { @Override public void run() { System.out.println("Traditional way"); } }; // Lambda expression way Runnable lambdaRunnable = () -> { System.out.println("Lambda way"); }; // Even simpler for single statements Runnable simpleLambda = () -> System.out.println("Simple lambda");

2. Lambda Syntax Variations

import java.util.*; import java.util.function.*; public class LambdaSyntax { public static void main(String[] args) { // 1. No parameters Supplier<String> supplier = () -> "Hello World"; // 2. Single parameter (parentheses optional) Consumer<String> consumer1 = (msg) -> System.out.println(msg); Consumer<String> consumer2 = msg -> System.out.println(msg); // No parentheses // 3. Multiple parameters BinaryOperator<Integer> adder = (a, b) -> a + b; // 4. Multiple parameters with explicit types BinaryOperator<Integer> typedAdder = (Integer a, Integer b) -> a + b; // 5. Multiple statements in body Function<String, String> processor = (str) -> { String trimmed = str.trim(); return trimmed.toUpperCase(); }; } }

3. Common Functional Interfaces with Lambdas

Predicate

import java.util.function.Predicate; import java.util.*; public class PredicateExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana"); // Lambda with Predicate Predicate<String> startsWithA = name -> name.startsWith("A"); Predicate<String> lengthGreaterThan3 = name -> name.length() > 3; // Using predicates names.stream() .filter(startsWithA) .forEach(System.out::println); // Combined predicates names.stream() .filter(startsWithA.and(lengthGreaterThan3)) .forEach(System.out::println); } }

Function

import java.util.function.Function; import java.util.*; public class FunctionExample { public static void main(String[] args) { List<String> words = Arrays.asList("hello", "world", "java", "lambda"); // Lambda with Function Function<String, Integer> lengthMapper = str -> str.length(); Function<String, String> upperCaseMapper = str -> str.toUpperCase(); // Using functions words.stream() .map(lengthMapper) .forEach(len -> System.out.println("Length: " + len)); words.stream() .map(upperCaseMapper) .forEach(System.out::println); } }

Consumer

import java.util.function.Consumer; import java.util.*; public class ConsumerExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // Lambda with Consumer Consumer<Integer> squarePrinter = num -> { int square = num * num; System.out.println(num + "² = " + square); }; Consumer<Integer> doublePrinter = num -> System.out.println("Double: " + (num * 2)); // Using consumers numbers.forEach(squarePrinter); // Chaining consumers numbers.forEach(squarePrinter.andThen(doublePrinter)); } }

Supplier

import java.util.function.Supplier; import java.util.Random; public class SupplierExample { public static void main(String[] args) { // Lambda with Supplier Supplier<Double> randomSupplier = () -> Math.random(); Supplier<String> greetingSupplier = () -> "Hello, World!"; // Using suppliers System.out.println("Random number: " + randomSupplier.get()); System.out.println("Greeting: " + greetingSupplier.get()); // More complex supplier Supplier<String> otpSupplier = () -> { Random random = new Random(); return String.format("%06d", random.nextInt(1000000)); }; System.out.println("OTP: " + otpSupplier.get()); } }

4. Method References with Lambdas

import java.util.*; import java.util.function.*; public class MethodReferenceExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana"); // Static method reference Function<String, Integer> parser = Integer::parseInt; // Instance method reference Consumer<String> printer = System.out::println; // Arbitrary object method reference names.forEach(String::toUpperCase); // Constructor reference Supplier<List<String>> listSupplier = ArrayList::new; Function<Integer, String[]> arraySupplier = String[]::new; } // Custom method for method reference public static boolean isEven(Integer number) { return number % 2 == 0; } }

5. Real-World Examples

Sorting with Lambdas

import java.util.*; public class SortingExample { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("John", 25), new Person("Alice", 30), new Person("Bob", 20) ); // Traditional sorting with anonymous class Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.getName().compareTo(p2.getName()); } }); // Sorting with lambda Collections.sort(people, (p1, p2) -> p1.getName().compareTo(p2.getName())); // Even simpler with Comparator.comparing people.sort(Comparator.comparing(Person::getName)); people.sort(Comparator.comparing(Person::getAge)); // Reverse sorting people.sort(Comparator.comparing(Person::getAge).reversed()); // Multiple criteria people.sort(Comparator.comparing(Person::getName) .thenComparing(Person::getAge)); } } class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return name + " (" + age + ")"; } }

Threading with Lambdas

public class ThreadExample { public static void main(String[] args) { // Traditional way Thread traditionalThread = new Thread(new Runnable() { @Override public void run() { System.out.println("Traditional thread"); } }); // Lambda way Thread lambdaThread = new Thread(() -> { System.out.println("Lambda thread"); // Multiple operations for (int i = 0; i < 5; i++) { System.out.println("Count: " + i); } }); traditionalThread.start(); lambdaThread.start(); } }

Collection Processing with Streams and Lambdas

import java.util.*; import java.util.stream.*; public class StreamLambdaExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // Filter even numbers and square them List<Integer> result = numbers.stream() .filter(n -> n % 2 == 0) // Lambda in filter .map(n -> n * n) // Lambda in map .collect(Collectors.toList()); System.out.println("Squared evens: " + result); // Group by even/odd Map<String, List<Integer>> grouped = numbers.stream() .collect(Collectors.groupingBy( n -> n % 2 == 0 ? "EVEN" : "ODD" // Lambda classifier )); System.out.println("Grouped: " + grouped); // Sum using reduce with lambda int sum = numbers.stream() .reduce(0, (a, b) -> a + b); // Lambda accumulator System.out.println("Sum: " + sum); } }

6. Custom Functional Interfaces

// Custom functional interface @FunctionalInterface interface StringProcessor { String process(String input); // Can have default methods default StringProcessor andThen(StringProcessor after) { return input -> after.process(this.process(input)); } // Can have static methods static StringProcessor toUpperCase() { return String::toUpperCase; } } public class CustomFunctionalInterface { public static void main(String[] args) { // Using custom functional interface with lambda StringProcessor trimmer = str -> str.trim(); StringProcessor upperCaser = str -> str.toUpperCase(); StringProcessor reverser = str -> new StringBuilder(str).reverse().toString(); String input = " hello world "; // Chain processors StringProcessor pipeline = trimmer.andThen(upperCaser).andThen(reverser); String result = pipeline.process(input); System.out.println("Original: '" + input + "'"); System.out.println("Processed: '" + result + "'"); // Using static method StringProcessor staticProcessor = StringProcessor.toUpperCase(); System.out.println(staticProcessor.process("hello")); } }

7. Variable Capture in Lambdas

import java.util.function.*; public class VariableCaptureExample { private String instanceVariable = "Instance variable"; public void demonstrateCapture() { String localVariable = "Local variable"; final String finalLocal = "Final local"; // Capturing instance variable Supplier<String> instanceCapture = () -> this.instanceVariable; // Capturing local variable (effectively final) Supplier<String> localCapture = () -> localVariable; // Capturing final local variable Supplier<String> finalCapture = () -> finalLocal; // Cannot modify captured variables // localVariable = "modified"; // This would make lambda invalid System.out.println(instanceCapture.get()); System.out.println(localCapture.get()); System.out.println(finalCapture.get()); } public static void main(String[] args) { new VariableCaptureExample().demonstrateCapture(); } }

8. Exception Handling in Lambdas

import java.util.function.*; import java.io.*; public class ExceptionHandlingLambda { // Functional interface that throws exception @FunctionalInterface interface FileProcessor { String process(File file) throws IOException; } public static void main(String[] args) { List<File> files = Arrays.asList(new File("file1.txt"), new File("file2.txt")); // Approach 1: Handle exception in lambda files.forEach(file -> { try { if (file.exists()) { System.out.println("File: " + file.getCanonicalPath()); } } catch (IOException e) { System.err.println("Error: " + e.getMessage()); } }); // Approach 2: Wrap in unchecked exception Function<File, String> safeProcessor = file -> { try { return file.getCanonicalPath(); } catch (IOException e) { throw new RuntimeException(e); } }; // Approach 3: Create wrapper method files.forEach(ExceptionHandlingLambda::processFileSafely); } private static void processFileSafely(File file) { try { System.out.println("Processing: " + file.getCanonicalPath()); } catch (IOException e) { System.err.println("Failed to process: " + file.getName()); } } }

Key Benefits of Lambda Expressions

  1. Concise Code - Reduces boilerplate code
  2. Readability - More expressive and readable code
  3. Functional Programming - Enables functional programming paradigms
  4. Parallel Processing - Easier parallel execution with streams
  5. API Design - Enables more flexible API designs

Best Practices

  1. Keep Lambdas Short - Prefer single-purpose lambdas
  2. Use Method References - When appropriate for better readability
  3. Avoid Complex Logic - Move complex logic to separate methods
  4. Consider Performance - Be mindful of variable capture overhead
  5. Use Descriptive Parameters - Meaningful parameter names in multi-parameter lambdas

Lambda expressions fundamentally changed Java programming by making code more concise, readable, and enabling functional programming styles while maintaining backward compatibility.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper