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.