Lambda expressions provide a clear and concise way to represent instances of single-method interfaces (functional interfaces) using expressions.
1. Basic Lambda Syntax
Basic Syntax Structure
import java.util.*; public class LambdaBasicSyntax { // Functional interfaces for demonstration @FunctionalInterface interface SimpleFunction { void execute(); } @FunctionalInterface interface StringProcessor { String process(String input); } @FunctionalInterface interface MathOperation { int operate(int a, int b); } @FunctionalInterface interface Validator { boolean isValid(String value); } public static void main(String[] args) { System.out.println("=== Basic Lambda Syntax ==="); // 1. No parameters, no return value SimpleFunction greet = () -> System.out.println("Hello, World!"); greet.execute(); // 2. Single parameter, no type declaration StringProcessor toUpper = s -> s.toUpperCase(); System.out.println("Uppercase: " + toUpper.process("hello")); // 3. Single parameter with parentheses (optional for single parameter) StringProcessor toLower = (s) -> s.toLowerCase(); System.out.println("Lowercase: " + toLower.process("HELLO")); // 4. Multiple parameters MathOperation addition = (a, b) -> a + b; System.out.println("5 + 3 = " + addition.operate(5, 3)); // 5. Multiple parameters with explicit types MathOperation subtraction = (int a, int b) -> a - b; System.out.println("5 - 3 = " + subtraction.operate(5, 3)); // 6. With return statement and curly braces MathOperation multiplication = (a, b) -> { int result = a * b; return result; }; System.out.println("5 * 3 = " + multiplication.operate(5, 3)); // 7. Single expression without return statement MathOperation division = (a, b) -> a / b; System.out.println("6 / 3 = " + division.operate(6, 3)); demonstrateMethodReferences(); } public static void demonstrateMethodReferences() { System.out.println("\n=== Method References vs Lambdas ==="); List<String> names = Arrays.asList("John", "Alice", "Bob", "Carol"); // Lambda expression names.forEach(name -> System.out.println(name)); // Method reference (equivalent) names.forEach(System.out::println); // Lambda for string transformation List<String> upperNames = new ArrayList<>(); names.forEach(name -> upperNames.add(name.toUpperCase())); // Method reference equivalent List<String> lowerNames = new ArrayList<>(); names.forEach(lowerNames::add); // Instance method reference System.out.println("Uppercase: " + upperNames); System.out.println("Lowercase: " + lowerNames); } } Lambda vs Anonymous Class
import java.util.*; public class LambdaVsAnonymous { @FunctionalInterface interface Calculator { int calculate(int x, int y); } @FunctionalInterface interface Greeter { String greet(String name); } @FunctionalInterface interface Condition { boolean test(int number); } public static void main(String[] args) { System.out.println("=== Lambda vs Anonymous Class ==="); // Anonymous class implementation Calculator anonymousAdd = new Calculator() { @Override public int calculate(int x, int y) { return x + y; } }; // Lambda equivalent Calculator lambdaAdd = (x, y) -> x + y; System.out.println("Anonymous: 5 + 3 = " + anonymousAdd.calculate(5, 3)); System.out.println("Lambda: 5 + 3 = " + lambdaAdd.calculate(5, 3)); // More complex example Greeter anonymousGreet = new Greeter() { @Override public String greet(String name) { return "Hello, " + name + "!"; } }; Greeter lambdaGreet = name -> "Hello, " + name + "!"; System.out.println("Anonymous: " + anonymousGreet.greet("John")); System.out.println("Lambda: " + lambdaGreet.greet("John")); // With multiple statements Condition anonymousCondition = new Condition() { @Override public boolean test(int number) { boolean isEven = number % 2 == 0; boolean isPositive = number > 0; return isEven && isPositive; } }; Condition lambdaCondition = number -> { boolean isEven = number % 2 == 0; boolean isPositive = number > 0; return isEven && isPositive; }; System.out.println("Anonymous test(4): " + anonymousCondition.test(4)); System.out.println("Lambda test(4): " + lambdaCondition.test(4)); demonstrateRunnableExample(); } public static void demonstrateRunnableExample() { System.out.println("\n=== Runnable Example ==="); // Anonymous class Runnable anonymousRunnable = new Runnable() { @Override public void run() { System.out.println("Running from anonymous class"); System.out.println("Thread: " + Thread.currentThread().getName()); } }; // Lambda equivalent Runnable lambdaRunnable = () -> { System.out.println("Running from lambda"); System.out.println("Thread: " + Thread.currentThread().getName()); }; // Execute both new Thread(anonymousRunnable).start(); new Thread(lambdaRunnable).start(); // Even shorter lambda new Thread(() -> System.out.println("Ultra short lambda")).start(); } } 2. Lambda Parameters and Return Types
Various Parameter Scenarios
import java.util.*; import java.util.function.*; public class LambdaParameters { public static void main(String[] args) { System.out.println("=== Lambda Parameters and Return Types ==="); // 1. No parameters Supplier<String> messageSupplier = () -> "Hello from supplier!"; System.out.println(messageSupplier.get()); Runnable noParamRunnable = () -> System.out.println("No parameters"); noParamRunnable.run(); // 2. Single parameter - type inference Function<String, Integer> stringLength = s -> s.length(); System.out.println("Length of 'Hello': " + stringLength.apply("Hello")); Consumer<String> printer = s -> System.out.println("Value: " + s); printer.accept("Test"); // 3. Single parameter with explicit type Function<String, String> explicitType = (String s) -> s.toUpperCase(); System.out.println("Explicit type: " + explicitType.apply("hello")); // 4. Multiple parameters BiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b; System.out.println("5 + 3 = " + adder.apply(5, 3)); // 5. Multiple parameters with explicit types BiFunction<String, String, String> concatenator = (String s1, String s2) -> s1 + " " + s2; System.out.println("Concatenated: " + concatenator.apply("Hello", "World")); // 6. No parameters with block body Supplier<Double> randomSupplier = () -> { Random random = new Random(); return random.nextDouble(); }; System.out.println("Random number: " + randomSupplier.get()); // 7. Complex multi-parameter lambda TriFunction<Integer, Integer, Integer, Integer> complexOperation = (a, b, c) -> { int sum = a + b + c; int product = a * b * c; return sum + product; }; System.out.println("Complex operation: " + complexOperation.apply(1, 2, 3)); demonstrateReturnTypes(); } // Custom functional interface for three parameters @FunctionalInterface interface TriFunction<T, U, V, R> { R apply(T t, U u, V v); } public static void demonstrateReturnTypes() { System.out.println("\n=== Return Type Examples ==="); // Implicit return (single expression) Function<Integer, Integer> square = x -> x * x; System.out.println("Square of 5: " + square.apply(5)); // Explicit return with block Function<Integer, String> numberInfo = x -> { if (x % 2 == 0) { return x + " is even"; } else { return x + " is odd"; } }; System.out.println(numberInfo.apply(4)); System.out.println(numberInfo.apply(7)); // Returning collections Function<Integer, List<Integer>> numberList = count -> { List<Integer> list = new ArrayList<>(); for (int i = 1; i <= count; i++) { list.add(i); } return list; }; System.out.println("Number list: " + numberList.apply(5)); // Void return type Consumer<List<String>> listProcessor = list -> { for (String item : list) { System.out.println("Processing: " + item.toUpperCase()); } }; listProcessor.accept(Arrays.asList("apple", "banana", "cherry")); } } 3. Built-in Functional Interfaces with Lambdas
java.util.function Package Examples
import java.util.*; import java.util.function.*; import java.util.stream.Collectors; public class BuiltInFunctionalInterfaces { public static void main(String[] args) { System.out.println("=== Built-in Functional Interfaces ==="); List<String> names = Arrays.asList("John", "Alice", "Bob", "Carol", "David", "Eve"); List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 1. Predicate - boolean valued function System.out.println("\n=== Predicate Examples ==="); Predicate<String> startsWithA = s -> s.startsWith("A"); Predicate<String> lengthGreaterThan3 = s -> s.length() > 3; List<String> filteredNames = names.stream() .filter(startsWithA.or(lengthGreaterThan3)) .collect(Collectors.toList()); System.out.println("Filtered names: " + filteredNames); // Predicate composition Predicate<Integer> isEven = n -> n % 2 == 0; Predicate<Integer> isGreaterThan5 = n -> n > 5; List<Integer> evenAndGreaterThan5 = numbers.stream() .filter(isEven.and(isGreaterThan5)) .collect(Collectors.toList()); System.out.println("Even and > 5: " + evenAndGreaterThan5); // 2. Function - transforms input to output System.out.println("\n=== Function Examples ==="); Function<String, Integer> stringLength = String::length; Function<String, String> toUpperCase = String::toUpperCase; Function<String, String> addPrefix = s -> "Mr. " + s; List<Integer> nameLengths = names.stream() .map(stringLength) .collect(Collectors.toList()); System.out.println("Name lengths: " + nameLengths); // Function chaining Function<String, String> nameTransformer = toUpperCase.andThen(addPrefix); List<String> transformedNames = names.stream() .map(nameTransformer) .collect(Collectors.toList()); System.out.println("Transformed names: " + transformedNames); // 3. Consumer - accepts input, returns void System.out.println("\n=== Consumer Examples ==="); Consumer<String> simplePrinter = System.out::println; Consumer<String> detailedPrinter = s -> System.out.println("Name: " + s + ", Length: " + s.length()); System.out.println("Simple printing:"); names.forEach(simplePrinter); System.out.println("\nDetailed printing:"); names.forEach(detailedPrinter); // Consumer chaining Consumer<String> chainedConsumer = simplePrinter.andThen(detailedPrinter); System.out.println("\nChained consumer:"); chainedConsumer.accept("Test"); // 4. Supplier - provides results System.out.println("\n=== Supplier Examples ==="); Supplier<Double> randomSupplier = Math::random; Supplier<List<String>> listSupplier = ArrayList::new; Supplier<String> constantSupplier = () -> "Constant Value"; System.out.println("Random: " + randomSupplier.get()); System.out.println("Constant: " + constantSupplier.get()); System.out.println("New list: " + listSupplier.get()); // 5. UnaryOperator and BinaryOperator System.out.println("\n=== Operator Examples ==="); UnaryOperator<String> stringDoubler = s -> s + s; BinaryOperator<Integer> multiplier = (a, b) -> a * b; System.out.println("Double 'Hi': " + stringDoubler.apply("Hi")); System.out.println("5 * 3 = " + multiplier.apply(5, 3)); demonstrateMoreFunctionalInterfaces(); } public static void demonstrateMoreFunctionalInterfaces() { System.out.println("\n=== More Functional Interfaces ==="); List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // BiFunction - two inputs, one output BiFunction<Integer, Integer, String> sumFormatter = (a, b) -> String.format("%d + %d = %d", a, b, a + b); System.out.println("Sum format: " + sumFormatter.apply(5, 3)); // BiConsumer - two inputs, no output BiConsumer<String, Integer> nameAgePrinter = (name, age) -> System.out.println(name + " is " + age + " years old"); nameAgePrinter.accept("John", 25); // BiPredicate - two inputs, boolean output BiPredicate<String, String> sameLength = (s1, s2) -> s1.length() == s2.length(); System.out.println("Same length? " + sameLength.test("hello", "world")); // Specialized functional interfaces IntFunction<String> intToString = i -> "Number: " + i; ToIntFunction<String> stringToLength = String::length; IntUnaryOperator square = x -> x * x; System.out.println(intToString.apply(42)); System.out.println("Length of 'Hello': " + stringToLength.applyAsInt("Hello")); System.out.println("Square of 9: " + square.applyAsInt(9)); } } 4. Lambda Expressions with Collections
Using Lambdas with Java Collections
import java.util.*; import java.util.stream.Collectors; public class LambdaWithCollections { static class Person { String name; int age; String city; public Person(String name, int age, String city) { this.name = name; this.age = age; this.city = city; } public String getName() { return name; } public int getAge() { return age; } public String getCity() { return city; } @Override public String toString() { return String.format("Person{name='%s', age=%d, city='%s'}", name, age, city); } } public static void main(String[] args) { System.out.println("=== Lambda Expressions with Collections ==="); List<Person> people = Arrays.asList( new Person("John", 25, "New York"), new Person("Alice", 30, "Boston"), new Person("Bob", 22, "Chicago"), new Person("Carol", 35, "New York"), new Person("David", 28, "Boston"), new Person("Eve", 19, "Chicago") ); // 1. Sorting with Comparator lambda System.out.println("\n=== Sorting ==="); // Sort by age (ascending) people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge())); System.out.println("Sorted by age:"); people.forEach(System.out::println); // Sort by name (descending) people.sort((p1, p2) -> p2.getName().compareTo(p1.getName())); System.out.println("\nSorted by name (descending):"); people.forEach(System.out::println); // Using Comparator.comparing with method reference people.sort(Comparator.comparing(Person::getCity).thenComparing(Person::getAge)); System.out.println("\nSorted by city then age:"); people.forEach(System.out::println); // 2. Filtering with Predicate System.out.println("\n=== Filtering ==="); List<Person> adults = people.stream() .filter(p -> p.getAge() >= 25) .collect(Collectors.toList()); System.out.println("Adults (25+): " + adults); List<Person> newYorkers = people.stream() .filter(p -> "New York".equals(p.getCity())) .collect(Collectors.toList()); System.out.println("New York residents: " + newYorkers); // 3. Mapping with Function System.out.println("\n=== Mapping ==="); List<String> names = people.stream() .map(Person::getName) .collect(Collectors.toList()); System.out.println("Names: " + names); List<String> nameAgeCombinations = people.stream() .map(p -> p.getName() + " (" + p.getAge() + ")") .collect(Collectors.toList()); System.out.println("Name with age: " + nameAgeCombinations); // 4. Grouping with Collectors System.out.println("\n=== Grouping ==="); Map<String, List<Person>> peopleByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity)); System.out.println("People by city:"); peopleByCity.forEach((city, cityPeople) -> { System.out.println(city + ": " + cityPeople); }); Map<String, Long> countByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.counting())); System.out.println("Count by city: " + countByCity); demonstrateMapOperations(); } public static void demonstrateMapOperations() { System.out.println("\n=== Map Operations with Lambdas ==="); Map<String, Integer> wordCounts = new HashMap<>(); wordCounts.put("apple", 3); wordCounts.put("banana", 5); wordCounts.put("cherry", 2); // forEach with BiConsumer System.out.println("Word counts:"); wordCounts.forEach((word, count) -> System.out.println(word + ": " + count)); // computeIfAbsent wordCounts.computeIfAbsent("date", k -> 1); wordCounts.computeIfAbsent("apple", k -> 10); // Won't compute, already exists System.out.println("After computeIfAbsent: " + wordCounts); // computeIfPresent wordCounts.computeIfPresent("banana", (word, count) -> count + 10); wordCounts.computeIfPresent("grape", (word, count) -> count + 1); // Won't compute, doesn't exist System.out.println("After computeIfPresent: " + wordCounts); // merge wordCounts.merge("apple", 5, (oldValue, newValue) -> oldValue + newValue); wordCounts.merge("fig", 1, (oldValue, newValue) -> oldValue + newValue); System.out.println("After merge: " + wordCounts); // replaceAll wordCounts.replaceAll((word, count) -> count * 2); System.out.println("After replaceAll (doubled): " + wordCounts); } } 5. Variable Capture in Lambdas
Effectively Final and Variable Capture
import java.util.*; import java.util.function.*; public class LambdaVariableCapture { private String instanceVariable = "Instance Variable"; private static String staticVariable = "Static Variable"; public static void main(String[] args) { System.out.println("=== Variable Capture in Lambdas ==="); LambdaVariableCapture example = new LambdaVariableCapture(); example.demonstrateLocalVariableCapture(); example.demonstrateInstanceAndStaticCapture(); example.demonstrateEffectivelyFinal(); } public void demonstrateLocalVariableCapture() { System.out.println("\n=== Local Variable Capture ==="); // Effectively final local variable final String finalLocal = "Final Local"; String effectivelyFinal = "Effectively Final"; // Lambda capturing local variables Supplier<String> localCapture = () -> { // Can read both final and effectively final variables return finalLocal + " and " + effectivelyFinal; }; System.out.println("Local capture: " + localCapture.get()); // Trying to modify captured variable (will cause compile error) // effectivelyFinal = "Modified"; // Uncommenting this will break the lambda // Array capture (array reference is effectively final, but contents can be modified) int[] counter = {0}; IntSupplier counterLambda = () -> ++counter[0]; // Modifying array content is allowed System.out.println("Counter: " + counterLambda.getAsInt()); System.out.println("Counter: " + counterLambda.getAsInt()); System.out.println("Counter: " + counterLambda.getAsInt()); } public void demonstrateInstanceAndStaticCapture() { System.out.println("\n=== Instance and Static Variable Capture ==="); // Lambda capturing instance variable Supplier<String> instanceCapture = () -> this.instanceVariable; System.out.println("Instance capture: " + instanceCapture.get()); // Lambda capturing static variable Supplier<String> staticCapture = () -> staticVariable; System.out.println("Static capture: " + staticCapture.get()); // Modifying instance variables from lambda Runnable instanceModifier = () -> { this.instanceVariable = "Modified from lambda"; staticVariable = "Static modified from lambda"; }; instanceModifier.run(); System.out.println("After modification:"); System.out.println("Instance: " + instanceCapture.get()); System.out.println("Static: " + staticCapture.get()); } public void demonstrateEffectivelyFinal() { System.out.println("\n=== Effectively Final Concept ==="); List<String> names = Arrays.asList("John", "Alice", "Bob"); // This works - list reference is effectively final names.forEach(name -> System.out.println(name)); // This would cause compile error - modifying effectively final variable // String prefix = "Mr. "; // names.forEach(name -> { // prefix = "Dr. "; // Compile error - cannot assign to effectively final variable // System.out.println(prefix + name); // }); // Workaround using array or wrapper String[] prefixArray = {"Mr. "}; names.forEach(name -> { System.out.println(prefixArray[0] + name); // Can modify array content, but not the array reference prefixArray[0] = "Dr. "; // This is allowed! }); // Using AtomicReference as wrapper java.util.concurrent.atomic.AtomicReference<String> prefixRef = new java.util.concurrent.atomic.AtomicReference<>("Hello "); names.forEach(name -> { String currentPrefix = prefixRef.get(); System.out.println(currentPrefix + name); prefixRef.set("Hi "); // This is allowed! }); } public void demonstrateThisInLambda() { System.out.println("\n=== 'this' in Lambda ==="); // In lambda, 'this' refers to the enclosing instance Runnable lambdaWithThis = () -> { System.out.println("Lambda this: " + this); System.out.println("Lambda this class: " + this.getClass().getSimpleName()); }; lambdaWithThis.run(); // Compare with anonymous class Runnable anonymousWithThis = new Runnable() { @Override public void run() { System.out.println("Anonymous this: " + this); System.out.println("Anonymous this class: " + this.getClass().getSimpleName()); } }; anonymousWithThis.run(); } } 6. Real-World Lambda Examples
Practical Lambda Usage Scenarios
import java.util.*; import java.util.concurrent.*; import java.util.function.*; import java.util.stream.*; public class RealWorldLambdaExamples { static class Product { String name; double price; String category; int stock; public Product(String name, double price, String category, int stock) { this.name = name; this.price = price; this.category = category; this.stock = stock; } public String getName() { return name; } public double getPrice() { return price; } public String getCategory() { return category; } public int getStock() { return stock; } @Override public String toString() { return String.format("Product{name='%-12s', price=$%-6.2f, category='%-10s', stock=%d}", name, price, category, stock); } } public static void main(String[] args) throws Exception { System.out.println("=== Real-World Lambda Examples ==="); List<Product> products = Arrays.asList( new Product("Laptop", 999.99, "Electronics", 10), new Product("Smartphone", 699.99, "Electronics", 25), new Product("Headphones", 149.99, "Electronics", 50), new Product("T-Shirt", 19.99, "Clothing", 100), new Product("Jeans", 49.99, "Clothing", 75), new Product("Book", 12.99, "Education", 200), new Product("Desk Lamp", 29.99, "Home", 30) ); // 1. E-commerce product filtering and transformation demonstrateECommerceOperations(products); // 2. Data processing pipeline demonstrateDataProcessing(products); // 3. Asynchronous operations with CompletableFuture demonstrateAsyncOperations(); // 4. Event handling and callbacks demonstrateEventHandling(); } public static void demonstrateECommerceOperations(List<Product> products) { System.out.println("\n=== E-Commerce Operations ==="); // Filter electronics products List<Product> electronics = products.stream() .filter(p -> "Electronics".equals(p.getCategory())) .collect(Collectors.toList()); System.out.println("Electronics products:"); electronics.forEach(System.out::println); // Apply discount to clothing products Function<Product, Product> applyClothingDiscount = p -> { if ("Clothing".equals(p.getCategory())) { return new Product(p.getName(), p.getPrice() * 0.8, p.getCategory(), p.getStock()); } return p; }; List<Product> discountedProducts = products.stream() .map(applyClothingDiscount) .collect(Collectors.toList()); System.out.println("\nProducts with clothing discount:"); discountedProducts.forEach(System.out::println); // Group products by category Map<String, List<Product>> productsByCategory = products.stream() .collect(Collectors.groupingBy(Product::getCategory)); System.out.println("\nProducts by category:"); productsByCategory.forEach((category, productList) -> { System.out.println(category + ": " + productList.size() + " products"); }); // Calculate total inventory value double totalValue = products.stream() .mapToDouble(p -> p.getPrice() * p.getStock()) .sum(); System.out.printf("\nTotal inventory value: $%.2f%n", totalValue); } public static void demonstrateDataProcessing(List<Product> products) { System.out.println("\n=== Data Processing Pipeline ==="); // Complex data processing pipeline Map<String, Double> categoryAveragePrice = products.stream() .collect(Collectors.groupingBy( Product::getCategory, Collectors.averagingDouble(Product::getPrice) )); System.out.println("Average price by category:"); categoryAveragePrice.forEach((category, avgPrice) -> System.out.printf("%-12s: $%.2f%n", category, avgPrice)); // Products that need restocking (stock < 20) Predicate<Product> needsRestocking = p -> p.getStock() < 20; List<String> productsToRestock = products.stream() .filter(needsRestocking) .map(Product::getName) .collect(Collectors.toList()); System.out.println("\nProducts needing restock: " + productsToRestock); // Price range analysis DoubleSummaryStatistics priceStats = products.stream() .mapToDouble(Product::getPrice) .summaryStatistics(); System.out.println("\nPrice statistics:"); System.out.printf("Count: %d, Min: $%.2f, Max: $%.2f, Average: $%.2f%n", priceStats.getCount(), priceStats.getMin(), priceStats.getMax(), priceStats.getAverage()); } public static void demonstrateAsyncOperations() throws Exception { System.out.println("\n=== Asynchronous Operations ==="); // Simulate async tasks with CompletableFuture CompletableFuture<String> asyncTask1 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); return "Result from async task 1"; } catch (InterruptedException e) { throw new RuntimeException(e); } }); CompletableFuture<String> asyncTask2 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(500); return "Result from async task 2"; } catch (InterruptedException e) { throw new RuntimeException(e); } }); // Combine results when both complete CompletableFuture<String> combined = asyncTask1.thenCombine(asyncTask2, (result1, result2) -> result1 + " + " + result2); System.out.println("Combined result: " + combined.get()); // Chain async operations CompletableFuture<Integer> chainedOperation = CompletableFuture.supplyAsync(() -> 5) .thenApplyAsync(x -> x * 2) .thenApplyAsync(x -> x + 10) .thenApplyAsync(x -> x * x); System.out.println("Chained operation result: " + chainedOperation.get()); } public static void demonstrateEventHandling() { System.out.println("\n=== Event Handling ==="); // Simple event system class EventManager { private Map<String, List<Consumer<String>>> listeners = new HashMap<>(); public void addListener(String eventType, Consumer<String> listener) { listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener); } public void fireEvent(String eventType, String data) { List<Consumer<String>> eventListeners = listeners.get(eventType); if (eventListeners != null) { eventListeners.forEach(listener -> listener.accept(data)); } } } EventManager eventManager = new EventManager(); // Register lambda event handlers eventManager.addListener("login", username -> System.out.println("User logged in: " + username)); eventManager.addListener("purchase", product -> System.out.println("Purchase made: " + product)); eventManager.addListener("error", errorMessage -> System.err.println("Error occurred: " + errorMessage)); // Fire events eventManager.fireEvent("login", "john_doe"); eventManager.fireEvent("purchase", "Laptop"); eventManager.fireEvent("error", "Connection timeout"); // Button click simulation (like in GUI programming) class Button { private String text; private Runnable onClick; public Button(String text) { this.text = text; } public void setOnClick(Runnable onClick) { this.onClick = onClick; } public void click() { System.out.println("Button '" + text + "' clicked"); if (onClick != null) { onClick.run(); } } } Button saveButton = new Button("Save"); saveButton.setOnClick(() -> { System.out.println("Saving data..."); System.out.println("Data saved successfully!"); }); Button cancelButton = new Button("Cancel"); cancelButton.setOnClick(() -> System.out.println("Operation cancelled")); // Simulate button clicks saveButton.click(); cancelButton.click(); } } Summary
Key Lambda Syntax Rules:
- Basic Syntax:
(parameters) -> expression - Parameter Rules:
- No parameters:
() -> expression - Single parameter:
param -> expressionor(param) -> expression - Multiple parameters:
(param1, param2) -> expression
- Body Rules:
- Single expression: implicit return
- Block body:
{ statements; return value; }
Important Concepts:
- Functional Interfaces: Lambdas work with interfaces having exactly one abstract method
- Type Inference: Compiler can often infer parameter types
- Variable Capture: Lambdas can capture effectively final variables
- Method References: Shorthand for common lambda patterns
Common Use Cases:
- Collection operations: filtering, mapping, sorting
- Event handling: GUI events, callbacks
- Asynchronous programming: CompletableFuture, threads
- Stream processing: data transformation pipelines
- Functional programming: higher-order functions
Best Practices:
- Keep lambdas short and focused
- Use method references when possible
- Avoid complex logic in lambdas
- Be mindful of variable capture
- Use meaningful parameter names
Lambda expressions make Java code more concise, readable, and functional, enabling modern programming paradigms while maintaining type safety.