Lambda Expressions and Functional Interfaces
Java Programming (4343203)
Lecture 20
Unit 5: Modern Java Features
GTU Computer Engineering Semester 4
Learning Objectives
- Understand functional programming concepts in Java 8+
- Master lambda expression syntax and usage patterns
- Work with built-in functional interfaces
- Create custom functional interfaces
- Apply method references and constructor references
- Integrate lambdas with collections and streams
Focus: Modern functional programming techniques to write more concise, readable, and maintainable Java code.
What are Lambda Expressions?
Traditional Anonymous Classes
// Before Java 8 - Anonymous class
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Hello from thread!");
}
};
// Comparator example
List names = Arrays.asList("John", "Jane", "Bob");
Collections.sort(names, new Comparator() {
@Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
});
// Event listener
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
Lambda Expressions (Java 8+)
// Java 8+ - Lambda expressions
Runnable task = () -> {
System.out.println("Hello from thread!");
};
// Even more concise
Runnable task = () -> System.out.println("Hello from thread!");
// Comparator with lambda
List names = Arrays.asList("John", "Jane", "Bob");
Collections.sort(names, (a, b) -> a.compareToIgnoreCase(b));
// Even shorter with method reference
Collections.sort(names, String::compareToIgnoreCase);
// Event listener with lambda
button.addActionListener(e -> System.out.println("Button clicked!"));
Benefits:
- Less verbose code
- Better readability
- Functional programming style
- Improved performance
Lambda Expression Syntax
public class LambdaSyntaxDemo {
public static void main(String[] args) {
System.out.println("=== Lambda Expression Syntax ===\n");
// 1. No parameters
Runnable noParams = () -> System.out.println("No parameters");
noParams.run();
// 2. Single parameter (parentheses optional)
Consumer singleParam = name -> System.out.println("Hello, " + name);
Consumer singleParamWithParens = (name) -> System.out.println("Hello, " + name);
singleParam.accept("Alice");
// 3. Multiple parameters
BinaryOperator add = (a, b) -> a + b;
System.out.println("5 + 3 = " + add.apply(5, 3));
// 4. Block body (multiple statements)
Consumer blockBody = (message) -> {
System.out.println("Processing: " + message);
System.out.println("Message length: " + message.length());
System.out.println("Uppercase: " + message.toUpperCase());
};
blockBody.accept("lambda expressions");
// 5. Returning values
Function getLength = str -> str.length();
Function getLengthBlock = str -> {
return str.length(); // explicit return needed in block
};
System.out.println("Length of 'Java': " + getLength.apply("Java"));
// 6. Type inference
BiFunction multiply = (x, y) -> x * y;
BiFunction multiplyExplicit = (Integer x, Integer y) -> x * y;
System.out.println("4 × 7 = " + multiply.apply(4, 7));
// 7. Lambda expressions in different contexts
demonstrateLambdaContexts();
}
private static void demonstrateLambdaContexts() {
System.out.println("\n--- Lambda in Different Contexts ---");
// In collections
List numbers = Arrays.asList(1, 2, 3, 4, 5);
// forEach
System.out.print("Numbers: ");
numbers.forEach(n -> System.out.print(n + " "));
System.out.println();
// removeIf
List mutableNumbers = new ArrayList<>(numbers);
mutableNumbers.removeIf(n -> n % 2 == 0);
System.out.println("Odd numbers: " + mutableNumbers);
// sort
List words = Arrays.asList("banana", "apple", "cherry", "date");
words.sort((a, b) -> Integer.compare(a.length(), b.length()));
System.out.println("Sorted by length: " + words);
// replaceAll
List names = Arrays.asList("alice", "bob", "charlie");
names.replaceAll(name -> name.toUpperCase());
System.out.println("Uppercase names: " + names);
}
}
Functional Interfaces
What is a Functional Interface?
- Interface with exactly one abstract method
- Can have default and static methods
- Annotated with
@FunctionalInterface - Target type for lambda expressions
Key Built-in Functional Interfaces
| Interface | Method | Purpose |
|---|---|---|
| Predicate<T> | boolean test(T) | Condition testing |
| Function<T,R> | R apply(T) | Transform input |
| Consumer<T> | void accept(T) | Consume input |
| Supplier<T> | T get() | Supply values |
| UnaryOperator<T> | T apply(T) | Same type transform |
| BinaryOperator<T> | T apply(T, T) | Combine two values |
import java.util.function.*;
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// Predicate - test conditions
Predicate isEven = x -> x % 2 == 0;
Predicate isEmpty = String::isEmpty;
System.out.println("Is 4 even? " + isEven.test(4));
System.out.println("Is '' empty? " + isEmpty.test(""));
// Function - transform data
Function stringLength = String::length;
Function intToHex = Integer::toHexString;
System.out.println("Length of 'Java': " + stringLength.apply("Java"));
System.out.println("255 in hex: " + intToHex.apply(255));
// Consumer - side effects
Consumer printer = System.out::println;
Consumer upperPrinter = s -> System.out.println(s.toUpperCase());
printer.accept("Hello World");
upperPrinter.accept("hello world");
// Supplier - provide values
Supplier randomUUID = () -> java.util.UUID.randomUUID().toString();
Supplier randomInt = () -> (int)(Math.random() * 100);
System.out.println("Random UUID: " + randomUUID.get());
System.out.println("Random int: " + randomInt.get());
// UnaryOperator - same type transformation
UnaryOperator toUpper = String::toUpperCase;
UnaryOperator square = x -> x * x;
System.out.println("Uppercase: " + toUpper.apply("java"));
System.out.println("5 squared: " + square.apply(5));
// BinaryOperator - combine two values
BinaryOperator add = Integer::sum;
BinaryOperator concat = String::concat;
System.out.println("3 + 7 = " + add.apply(3, 7));
System.out.println("Concat: " + concat.apply("Hello", "World"));
}
}
Creating Custom Functional Interfaces
// Custom functional interfaces
@FunctionalInterface
interface MathOperation {
double calculate(double a, double b);
// Default methods are allowed
default String getDescription() {
return "A mathematical operation";
}
// Static methods are allowed
static MathOperation getAddition() {
return (a, b) -> a + b;
}
}
@FunctionalInterface
interface StringProcessor {
String process(String input);
}
@FunctionalInterface
interface TriFunction {
R apply(T t, U u, V v);
}
@FunctionalInterface
interface Validator {
ValidationResult validate(T input);
}
// Supporting classes
class ValidationResult {
private boolean valid;
private String message;
public ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
public boolean isValid() { return valid; }
public String getMessage() { return message; }
@Override
public String toString() {
return String.format("ValidationResult{valid=%s, message='%s'}", valid, message);
}
}
public class CustomFunctionalInterfaceDemo {
public static void main(String[] args) {
System.out.println("=== Custom Functional Interfaces ===\n");
// MathOperation examples
MathOperation addition = (a, b) -> a + b;
MathOperation multiplication = (a, b) -> a * b;
MathOperation power = Math::pow;
System.out.println("5 + 3 = " + addition.calculate(5, 3));
System.out.println("5 × 3 = " + multiplication.calculate(5, 3));
System.out.println("2^8 = " + power.calculate(2, 8));
// Using static method
MathOperation staticAddition = MathOperation.getAddition();
System.out.println("Static addition 10 + 15 = " + staticAddition.calculate(10, 15));
// StringProcessor examples
StringProcessor reverser = s -> new StringBuilder(s).reverse().toString();
StringProcessor capitalizer = s -> s.toUpperCase();
StringProcessor addPrefix = s -> "PREFIX_" + s;
String input = "Hello World";
System.out.println("Original: " + input);
System.out.println("Reversed: " + reverser.process(input));
System.out.println("Capitalized: " + capitalizer.process(input));
System.out.println("With prefix: " + addPrefix.process(input));
// TriFunction example
TriFunction substring = String::substring;
TriFunction addThree = (a, b, c) -> a + b + c;
System.out.println("Substring of 'Programming' from 3 to 7: " + substring.apply("Programming", 3, 7));
System.out.println("Sum of 5, 10, 15: " + addThree.apply(5, 10, 15));
// Validator examples
demonstrateValidators();
// Calculator with custom operations
demonstrateCalculator();
}
private static void demonstrateValidators() {
System.out.println("\n--- Custom Validators ---");
// Email validator
Validator emailValidator = email -> {
if (email == null || email.trim().isEmpty()) {
return new ValidationResult(false, "Email cannot be empty");
}
if (!email.contains("@") || !email.contains(".")) {
return new ValidationResult(false, "Invalid email format");
}
return new ValidationResult(true, "Email is valid");
};
// Age validator
Validator ageValidator = age -> {
if (age == null) {
return new ValidationResult(false, "Age cannot be null");
}
if (age < 0 || age > 150) {
return new ValidationResult(false, "Age must be between 0 and 150");
}
return new ValidationResult(true, "Age is valid");
};
// Test validators
String[] emails = {"user@example.com", "invalid-email", "", null};
Integer[] ages = {25, -5, 200, null, 30};
System.out.println("Email validation:");
for (String email : emails) {
ValidationResult result = emailValidator.validate(email);
System.out.printf(" %-20s -> %s%n", email, result);
}
System.out.println("\nAge validation:");
for (Integer age : ages) {
ValidationResult result = ageValidator.validate(age);
System.out.printf(" %-5s -> %s%n", age, result);
}
}
private static void demonstrateCalculator() {
System.out.println("\n--- Custom Calculator ---");
Calculator calc = new Calculator();
// Register custom operations
calc.registerOperation("add", (a, b) -> a + b);
calc.registerOperation("multiply", (a, b) -> a * b);
calc.registerOperation("power", Math::pow);
calc.registerOperation("max", Math::max);
calc.registerOperation("min", Math::min);
// Perform calculations
System.out.println("10 add 5 = " + calc.calculate("add", 10, 5));
System.out.println("3 power 4 = " + calc.calculate("power", 3, 4));
System.out.println("max of 15 and 8 = " + calc.calculate("max", 15, 8));
// List available operations
System.out.println("Available operations: " + calc.getAvailableOperations());
}
}
// Calculator class using functional interfaces
class Calculator {
private Map operations = new HashMap<>();
public void registerOperation(String name, MathOperation operation) {
operations.put(name, operation);
}
public double calculate(String operation, double a, double b) {
MathOperation op = operations.get(operation);
if (op == null) {
throw new IllegalArgumentException("Unknown operation: " + operation);
}
return op.calculate(a, b);
}
public Set getAvailableOperations() {
return operations.keySet();
}
}
Method References
import java.util.*;
import java.util.function.*;
public class MethodReferencesDemo {
public static void main(String[] args) {
System.out.println("=== Method References ===\n");
// 1. Static method references
demonstrateStaticMethodReferences();
// 2. Instance method references
demonstrateInstanceMethodReferences();
// 3. Constructor references
demonstrateConstructorReferences();
// 4. Method references with collections
demonstrateMethodReferencesWithCollections();
}
private static void demonstrateStaticMethodReferences() {
System.out.println("--- Static Method References ---");
// Static method reference: ClassName::methodName
Function parseInt = Integer::parseInt;
Function round = Math::round;
BinaryOperator max = Integer::max;
System.out.println("Parse '123': " + parseInt.apply("123"));
System.out.println("Round 3.7: " + round.apply(3.7));
System.out.println("Max of 5 and 8: " + max.apply(5, 8));
// Comparison with lambda
Function parseIntLambda = s -> Integer.parseInt(s);
System.out.println("Lambda equivalent: " + parseIntLambda.apply("456"));
System.out.println();
}
private static void demonstrateInstanceMethodReferences() {
System.out.println("--- Instance Method References ---");
// Instance method reference of particular object: instance::methodName
String greeting = "Hello, World!";
Supplier toUpperCase = greeting::toUpperCase;
Supplier getLength = greeting::length;
System.out.println("Original: " + greeting);
System.out.println("Uppercase: " + toUpperCase.get());
System.out.println("Length: " + getLength.get());
// Instance method reference of arbitrary object: ClassName::methodName
Function toUpper = String::toUpperCase;
Function stringLength = String::length;
BiPredicate startsWith = String::startsWith;
System.out.println("Arbitrary object uppercase: " + toUpper.apply("java"));
System.out.println("Arbitrary object length: " + stringLength.apply("programming"));
System.out.println("'Java' starts with 'Ja': " + startsWith.test("Java", "Ja"));
// Custom object method references
Person person = new Person("Alice", 25);
Supplier getName = person::getName;
Supplier getAge = person::getAge;
System.out.println("Person name: " + getName.get());
System.out.println("Person age: " + getAge.get());
System.out.println();
}
private static void demonstrateConstructorReferences() {
System.out.println("--- Constructor References ---");
// Constructor reference: ClassName::new
Supplier sbSupplier = StringBuilder::new;
Function sbFromString = StringBuilder::new;
BiFunction personCreator = Person::new;
StringBuilder sb1 = sbSupplier.get();
sb1.append("Created with constructor reference");
System.out.println("StringBuilder: " + sb1);
StringBuilder sb2 = sbFromString.apply("Initial content");
System.out.println("StringBuilder with content: " + sb2);
Person newPerson = personCreator.apply("Bob", 30);
System.out.println("Created person: " + newPerson);
// Array constructor reference
Function intArrayCreator = int[]::new;
Function stringArrayCreator = String[]::new;
int[] intArray = intArrayCreator.apply(5);
String[] stringArray = stringArrayCreator.apply(3);
System.out.println("Created int array of length: " + intArray.length);
System.out.println("Created String array of length: " + stringArray.length);
System.out.println();
}
private static void demonstrateMethodReferencesWithCollections() {
System.out.println("--- Method References with Collections ---");
List words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
// forEach with method reference
System.out.print("Words: ");
words.forEach(System.out::print);
System.out.println();
// map with method reference
List lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Word lengths: " + lengths);
// filter with method reference
Predicate isEmpty = String::isEmpty;
List nonEmptyWords = words.stream()
.filter(isEmpty.negate())
.collect(Collectors.toList());
System.out.println("Non-empty words: " + nonEmptyWords);
// sort with method reference
List people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 20)
);
people.sort(Comparator.comparing(Person::getAge));
System.out.println("People sorted by age: " + people);
// Creating objects from strings
List personStrings = Arrays.asList("David,35", "Eve,28", "Frank,32");
List personsFromStrings = personStrings.stream()
.map(s -> s.split(","))
.map(parts -> new Person(parts[0], Integer.parseInt(parts[1])))
.collect(Collectors.toList());
System.out.println("Persons from strings: " + personsFromStrings);
System.out.println();
}
}
// Supporting class
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 String.format("%s(%d)", name, age);
}
}
GTU Previous Year Question (Summer 2022)
Q: Write a Java program to demonstrate lambda expressions and functional interfaces. Create a custom functional interface for mathematical operations and show different ways to implement it using lambda expressions. Also demonstrate the use of built-in functional interfaces like Predicate, Function, and Consumer.
Solution:
import java.util.*;
import java.util.function.*;
// Custom functional interface for mathematical operations
@FunctionalInterface
interface MathOperation {
double execute(double a, double b);
// Default method
default String getOperationType() {
return "Mathematical Operation";
}
// Static method for creating common operations
static MathOperation createAddition() {
return (a, b) -> a + b;
}
static MathOperation createMultiplication() {
return (a, b) -> a * b;
}
}
// Another custom functional interface
@FunctionalInterface
interface NumberProcessor {
double process(double number);
}
// Custom functional interface for validation
@FunctionalInterface
interface NumberValidator {
boolean isValid(double number);
}
public class LambdaFunctionalInterfaceDemo {
public static void main(String[] args) {
System.out.println("=== Lambda Expressions and Functional Interfaces Demo ===\n");
// Demonstrate custom functional interface
demonstrateCustomFunctionalInterface();
// Demonstrate built-in functional interfaces
demonstrateBuiltInFunctionalInterfaces();
// Demonstrate practical examples
demonstratePracticalExamples();
}
private static void demonstrateCustomFunctionalInterface() {
System.out.println("--- Custom Functional Interface: MathOperation ---");
// Different ways to implement MathOperation using lambdas
// 1. Addition
MathOperation addition = (a, b) -> a + b;
// 2. Subtraction with explicit return
MathOperation subtraction = (a, b) -> {
return a - b;
};
// 3. Multiplication
MathOperation multiplication = (a, b) -> a * b;
// 4. Division with validation
MathOperation division = (a, b) -> {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
};
// 5. Power operation using Math.pow
MathOperation power = Math::pow; // Method reference
// 6. Maximum operation
MathOperation maximum = Math::max;
// 7. Custom complex operation
MathOperation customOperation = (a, b) -> {
double result = Math.sqrt(a * a + b * b); // Euclidean distance
System.out.println("Computing Euclidean distance of " + a + " and " + b);
return result;
};
// Test all operations
double x = 12.0, y = 5.0;
System.out.printf("Numbers: %.1f and %.1f%n", x, y);
System.out.printf("Addition: %.1f + %.1f = %.2f%n", x, y, addition.execute(x, y));
System.out.printf("Subtraction: %.1f - %.1f = %.2f%n", x, y, subtraction.execute(x, y));
System.out.printf("Multiplication: %.1f × %.1f = %.2f%n", x, y, multiplication.execute(x, y));
System.out.printf("Division: %.1f ÷ %.1f = %.2f%n", x, y, division.execute(x, y));
System.out.printf("Power: %.1f ^ %.1f = %.2f%n", x, y, power.execute(x, y));
System.out.printf("Maximum: max(%.1f, %.1f) = %.2f%n", x, y, maximum.execute(x, y));
System.out.printf("Custom (Euclidean): %.2f%n", customOperation.execute(x, y));
// Using static methods
MathOperation staticAdd = MathOperation.createAddition();
MathOperation staticMult = MathOperation.createMultiplication();
System.out.printf("Static addition: %.1f + %.1f = %.2f%n", x, y, staticAdd.execute(x, y));
System.out.printf("Static multiplication: %.1f × %.1f = %.2f%n", x, y, staticMult.execute(x, y));
// Calculator using custom interface
Calculator calculator = new Calculator();
calculator.performOperations();
System.out.println();
}
private static void demonstrateBuiltInFunctionalInterfaces() {
System.out.println("--- Built-in Functional Interfaces ---");
// 1. Predicate - boolean test(T t)
System.out.println("=== Predicate Examples ===");
Predicate isEven = n -> n % 2 == 0;
Predicate isPositive = n -> n > 0;
Predicate isEmpty = String::isEmpty;
Predicate isLongWord = word -> word.length() > 5;
// Combine predicates
Predicate isEvenAndPositive = isEven.and(isPositive);
Predicate isOddOrNegative = isEven.negate().or(isPositive.negate());
List numbers = Arrays.asList(-2, -1, 0, 1, 2, 3, 4, 5, 6);
List words = Arrays.asList("", "hi", "hello", "world", "programming", "java");
System.out.println("Numbers: " + numbers);
System.out.print("Even numbers: ");
numbers.stream().filter(isEven).forEach(n -> System.out.print(n + " "));
System.out.println();
System.out.print("Even and positive: ");
numbers.stream().filter(isEvenAndPositive).forEach(n -> System.out.print(n + " "));
System.out.println();
System.out.println("Words: " + words);
System.out.print("Long words: ");
words.stream().filter(isLongWord).forEach(w -> System.out.print(w + " "));
System.out.println();
// 2. Function - R apply(T t)
System.out.println("\n=== Function Examples ===");
Function stringLength = String::length;
Function intToHex = Integer::toHexString;
Function toUpperCase = String::toUpperCase;
Function roundToInt = d -> (int) Math.round(d);
// Function composition
Function lengthToHex = stringLength.andThen(intToHex);
Function upperCaseLength = toUpperCase.compose(s -> s + " (original)");
List testWords = Arrays.asList("Java", "Lambda", "Function", "Programming");
System.out.println("Original words: " + testWords);
testWords.forEach(word -> {
System.out.printf("%-12s -> Length: %d, Hex: %s, Upper: %s%n",
word,
stringLength.apply(word),
lengthToHex.apply(word),
toUpperCase.apply(word)
);
});
List decimals = Arrays.asList(3.14, 2.718, 1.414, 0.707);
System.out.println("Decimals: " + decimals);
System.out.print("Rounded to integers: ");
decimals.stream().map(roundToInt).forEach(i -> System.out.print(i + " "));
System.out.println();
// 3. Consumer - void accept(T t)
System.out.println("\n=== Consumer Examples ===");
Consumer printWithBrackets = s -> System.out.println("[" + s + "]");
Consumer printLength = s -> System.out.println("Length of '" + s + "' is " + s.length());
Consumer printSquare = n -> System.out.println(n + "² = " + (n * n));
// Chain consumers
Consumer combinedConsumer = printWithBrackets.andThen(printLength);
List names = Arrays.asList("Alice", "Bob", "Charlie");
System.out.println("Processing names with combined consumer:");
names.forEach(combinedConsumer);
System.out.println("Processing numbers:");
Arrays.asList(1, 2, 3, 4, 5).forEach(printSquare);
// 4. Supplier - T get()
System.out.println("\n=== Supplier Examples ===");
Supplier currentTime = () -> new Date().toString();
Supplier randomNumber = () -> (int) (Math.random() * 100);
Supplier greeting = () -> "Hello from Supplier!";
System.out.println("Current time: " + currentTime.get());
System.out.println("Random number: " + randomNumber.get());
System.out.println("Greeting: " + greeting.get());
// Using supplier for lazy evaluation
List lazyNumbers = generateNumbers(5, randomNumber);
System.out.println("Lazy generated numbers: " + lazyNumbers);
System.out.println();
}
private static List generateNumbers(int count, Supplier numberSupplier) {
List numbers = new ArrayList<>();
for (int i = 0; i < count; i++) {
numbers.add(numberSupplier.get());
}
return numbers;
}
}
Practical Examples Implementation:
private static void demonstratePracticalExamples() {
System.out.println("--- Practical Examples ---");
// Student grade processing system
StudentGradeProcessor processor = new StudentGradeProcessor();
processor.demonstrateGradeProcessing();
// Event handling system
EventHandlingSystem eventSystem = new EventHandlingSystem();
eventSystem.demonstrateEventHandling();
// Data validation system
DataValidationSystem validationSystem = new DataValidationSystem();
validationSystem.demonstrateValidation();
}
}
// Calculator class using functional interfaces
class Calculator {
private Map operations = new HashMap<>();
public Calculator() {
// Register operations using lambdas
operations.put("add", (a, b) -> a + b);
operations.put("subtract", (a, b) -> a - b);
operations.put("multiply", (a, b) -> a * b);
operations.put("divide", (a, b) -> {
if (b == 0) throw new ArithmeticException("Division by zero");
return a / b;
});
operations.put("power", Math::pow);
operations.put("mod", (a, b) -> a % b);
}
public double calculate(String operation, double a, double b) {
MathOperation op = operations.get(operation.toLowerCase());
if (op == null) {
throw new IllegalArgumentException("Unknown operation: " + operation);
}
return op.execute(a, b);
}
public void performOperations() {
System.out.println("\n--- Calculator Demo ---");
double x = 20.0, y = 4.0;
operations.forEach((name, operation) -> {
try {
double result = operation.execute(x, y);
System.out.printf("%-10s: %.1f %s %.1f = %.2f%n",
name, x, getSymbol(name), y, result);
} catch (Exception e) {
System.out.printf("%-10s: Error - %s%n", name, e.getMessage());
}
});
}
private String getSymbol(String operation) {
switch (operation) {
case "add": return "+";
case "subtract": return "-";
case "multiply": return "×";
case "divide": return "÷";
case "power": return "^";
case "mod": return "%";
default: return "?";
}
}
}
// Student grade processing system
class StudentGradeProcessor {
public void demonstrateGradeProcessing() {
System.out.println("\n=== Student Grade Processing ===");
List students = Arrays.asList(
new Student("Alice", 85),
new Student("Bob", 92),
new Student("Charlie", 78),
new Student("Diana", 96),
new Student("Eve", 67)
);
// Predicate for different grade categories
Predicate isExcellent = student -> student.getGrade() >= 90;
Predicate isGood = student -> student.getGrade() >= 80;
Predicate needsImprovement = student -> student.getGrade() < 70;
// Function to get grade letter
Function getGradeLetter = grade -> {
if (grade >= 90) return "A";
if (grade >= 80) return "B";
if (grade >= 70) return "C";
if (grade >= 60) return "D";
return "F";
};
// Consumer for printing student info
Consumer printStudentInfo = student ->
System.out.printf("%-10s: %3d (%s)%n",
student.getName(),
student.getGrade(),
getGradeLetter.apply(student.getGrade())
);
System.out.println("All students:");
students.forEach(printStudentInfo);
System.out.println("\nExcellent students (A grade):");
students.stream().filter(isExcellent).forEach(printStudentInfo);
System.out.println("\nStudents needing improvement:");
students.stream().filter(needsImprovement).forEach(printStudentInfo);
// Statistics using built-in functions
double averageGrade = students.stream()
.mapToInt(Student::getGrade)
.average()
.orElse(0.0);
System.out.printf("Class average: %.2f%n", averageGrade);
}
}
// Event handling system
class EventHandlingSystem {
public void demonstrateEventHandling() {
System.out.println("\n=== Event Handling System ===");
// Event handlers using functional interfaces
Consumer logHandler = event -> System.out.println("LOG: " + event);
Consumer emailHandler = event -> System.out.println("EMAIL: Sending notification for " + event);
Consumer databaseHandler = event -> System.out.println("DB: Recording " + event);
// Combine handlers
Consumer combinedHandler = logHandler.andThen(emailHandler).andThen(databaseHandler);
// Event filter
Predicate isImportantEvent = event ->
event.contains("ERROR") || event.contains("CRITICAL");
List events = Arrays.asList(
"User login successful",
"ERROR: Database connection failed",
"File uploaded",
"CRITICAL: Server overload detected",
"User logout"
);
System.out.println("Processing all events:");
events.forEach(event -> {
System.out.println("Event: " + event);
if (isImportantEvent.test(event)) {
System.out.println(" -> IMPORTANT EVENT - Full processing");
combinedHandler.accept(event);
} else {
System.out.println(" -> Regular event - Log only");
logHandler.accept(event);
}
System.out.println();
});
}
}
// Data validation system
class DataValidationSystem {
public void demonstrateValidation() {
System.out.println("\n=== Data Validation System ===");
// Validation predicates
Predicate isValidEmail = email ->
email != null && email.contains("@") && email.contains(".");
Predicate isValidPassword = password ->
password != null && password.length() >= 8;
Predicate isValidAge = age ->
age != null && age >= 0 && age <= 150;
// Validation functions that return messages
Function validateEmail = email ->
isValidEmail.test(email) ? "Email is valid" : "Invalid email format";
Function validatePassword = password ->
isValidPassword.test(password) ? "Password is valid" : "Password must be at least 8 characters";
Function validateAge = age ->
isValidAge.test(age) ? "Age is valid" : "Age must be between 0 and 150";
// Test data
String[] emails = {"user@example.com", "invalid-email", "", null};
String[] passwords = {"password123", "short", "verylongpassword", null};
Integer[] ages = {25, -5, 200, null, 0, 150};
System.out.println("Email validation:");
for (String email : emails) {
System.out.printf(" %-20s -> %s%n", email, validateEmail.apply(email));
}
System.out.println("\nPassword validation:");
for (String password : passwords) {
System.out.printf(" %-20s -> %s%n", password, validatePassword.apply(password));
}
System.out.println("\nAge validation:");
for (Integer age : ages) {
System.out.printf(" %-20s -> %s%n", age, validateAge.apply(age));
}
}
}
// Supporting classes
class Student {
private String name;
private int grade;
public Student(String name, int grade) {
this.name = name;
this.grade = grade;
}
public String getName() { return name; }
public int getGrade() { return grade; }
@Override
public String toString() {
return String.format("%s(%d)", name, grade);
}
}
Solution Features:
- Custom MathOperation functional interface with multiple implementations
- Comprehensive demonstration of Predicate, Function, Consumer, and Supplier
- Practical examples: Calculator, Student grades, Event handling, Validation
- Method references and function composition
- Real-world applications of functional programming concepts
📚 Lecture Summary
Key Concepts Covered
- Lambda expression syntax and usage
- Functional interfaces and @FunctionalInterface
- Built-in functional interfaces (Predicate, Function, Consumer, Supplier)
- Method references (static, instance, constructor)
- Custom functional interfaces
- Function composition and chaining
Benefits and Best Practices
- More concise and readable code
- Functional programming paradigm
- Better integration with collections and streams
- Improved code reusability
- Use appropriate functional interface for the task
- Prefer method references when applicable
🎯 Next Lecture Preview
Lecture 21: Stream API and Parallel Processing
- Stream creation and operations
- Intermediate and terminal operations
- Collectors and grouping
- Parallel streams and performance

