Functional Interfaces in Java

Introduction

Imagine you have a universal remote control that can work with any device as long as it has a power button. Functional Interfaces in Java work exactly like that—they define a single "button" (method) that can be implemented in many different ways, making your code incredibly flexible and powerful!

Functional interfaces are the foundation of Java's lambda expressions and method references, enabling functional programming paradigms in an object-oriented language. They're like contracts for behavior that can be passed around as data.


What are Functional Interfaces?

Functional interfaces are interfaces that contain exactly one abstract method (but can have multiple default or static methods). They enable lambda expressions and represent single units of behavior that can be passed as parameters, returned from methods, or stored in variables.

Key Characteristics:

  • Single abstract method: Exactly one unimplemented method
  • @FunctionalInterface annotation: Optional but recommended
  • Lambda compatible: Can be implemented using lambda expressions
  • Method reference support: Can use existing methods as implementations
  • Built-in interfaces: Java provides many common functional interfaces

Built-in Functional Interfaces in Java

InterfaceAbstract MethodDescriptionCommon Use
Function<T,R>R apply(T t)Transforms input to outputData transformation
Predicate<T>boolean test(T t)Tests a conditionFiltering, validation
Consumer<T>void accept(T t)Consumes without returningSide effects, logging
Supplier<T>T get()Provides a valueFactory, lazy generation
UnaryOperator<T>T apply(T t)Same type transformationModifying objects
BinaryOperator<T>T apply(T t1, T t2)Combines two valuesReduction, math operations

Code Explanation with Examples

Example 1: Basic Functional Interfaces

import java.util.function.*;
public class BasicFunctionalInterfaces {
public static void main(String[] args) {
System.out.println("=== BASIC FUNCTIONAL INTERFACES ===");
// 1. Function<T, R> - transforms input to output
System.out.println("\n1. FUNCTION INTERFACE:");
Function<String, Integer> stringLength = str -> str.length();
System.out.println("Length of 'Hello': " + stringLength.apply("Hello"));
Function<Integer, String> numberToString = num -> "Number: " + num;
System.out.println(numberToString.apply(42));
// 2. Predicate<T> - tests a condition
System.out.println("\n2. PREDICATE INTERFACE:");
Predicate<String> isLong = str -> str.length() > 5;
System.out.println("Is 'Java' long? " + isLong.test("Java"));
System.out.println("Is 'Functional' long? " + isLong.test("Functional"));
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println("Is 7 even? " + isEven.test(7));
System.out.println("Is 8 even? " + isEven.test(8));
// 3. Consumer<T> - performs action without returning
System.out.println("\n3. CONSUMER INTERFACE:");
Consumer<String> printer = message -> System.out.println("📢 " + message);
printer.accept("Hello, World!");
printer.accept("Functional programming is fun!");
Consumer<Integer> squarePrinter = num -> 
System.out.println(num + "² = " + (num * num));
squarePrinter.accept(5);
squarePrinter.accept(9);
// 4. Supplier<T> - provides values
System.out.println("\n4. SUPPLIER INTERFACE:");
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println("Random 1: " + randomSupplier.get());
System.out.println("Random 2: " + randomSupplier.get());
Supplier<String> greetingSupplier = () -> "Hello from Supplier!";
System.out.println(greetingSupplier.get());
// 5. UnaryOperator<T> - same type transformation
System.out.println("\n5. UNARY OPERATOR:");
UnaryOperator<String> toUpperCase = str -> str.toUpperCase();
System.out.println("Uppercase: " + toUpperCase.apply("hello world"));
UnaryOperator<Integer> square = num -> num * num;
System.out.println("5 squared: " + square.apply(5));
// 6. BinaryOperator<T> - combines two values
System.out.println("\n6. BINARY OPERATOR:");
BinaryOperator<Integer> adder = (a, b) -> a + b;
System.out.println("5 + 3 = " + adder.apply(5, 3));
BinaryOperator<String> concatenator = (s1, s2) -> s1 + " " + s2;
System.out.println("Concatenated: " + concatenator.apply("Hello", "Java"));
}
}

Output:

=== BASIC FUNCTIONAL INTERFACES ===
1. FUNCTION INTERFACE:
Length of 'Hello': 5
Number: 42
2. PREDICATE INTERFACE:
Is 'Java' long? false
Is 'Functional' long? true
Is 7 even? false
Is 8 even? true
3. CONSUMER INTERFACE:
📢 Hello, World!
📢 Functional programming is fun!
5² = 25
9² = 81
4. SUPPLIER INTERFACE:
Random 1: 0.7421384629198332
Random 2: 0.1854682736451928
Hello from Supplier!
5. UNARY OPERATOR:
Uppercase: HELLO WORLD
5 squared: 25
6. BINARY OPERATOR:
5 + 3 = 8
Concatenated: Hello Java

Example 2: Custom Functional Interfaces

import java.util.*;
public class CustomFunctionalInterfaces {
public static void main(String[] args) {
System.out.println("=== CUSTOM FUNCTIONAL INTERFACES ===");
// Using custom functional interfaces
System.out.println("\n1. STRING PROCESSOR:");
StringProcessor toUpper = str -> str.toUpperCase();
StringProcessor addExclamation = str -> str + "!";
System.out.println("Upper: " + toUpper.process("hello"));
System.out.println("Excited: " + addExclamation.process("Wow"));
// Chain processors
String result = toUpper.andThen(addExclamation).process("functional");
System.out.println("Chained: " + result);
System.out.println("\n2. VALIDATOR:");
Validator<String> emailValidator = email -> 
email != null && email.contains("@") && email.contains(".");
Validator<Integer> ageValidator = age -> age >= 0 && age <= 150;
System.out.println("Email valid: " + emailValidator.validate("[email protected]"));
System.out.println("Age valid: " + ageValidator.validate(25));
System.out.println("Age invalid: " + ageValidator.validate(-5));
System.out.println("\n3. CALCULATOR:");
Calculator adder = (a, b) -> a + b;
Calculator multiplier = (a, b) -> a * b;
Calculator power = (a, b) -> (int) Math.pow(a, b);
System.out.println("5 + 3 = " + adder.calculate(5, 3));
System.out.println("5 * 3 = " + multiplier.calculate(5, 3));
System.out.println("2 ^ 8 = " + power.calculate(2, 8));
System.out.println("\n4. TRANSFORMER:");
Transformer<String, Integer> stringToLength = String::length;
Transformer<Integer, String> numberToBinary = num -> Integer.toBinaryString(num);
System.out.println("'Hello' length: " + stringToLength.transform("Hello"));
System.out.println("42 in binary: " + numberToBinary.transform(42));
System.out.println("\n5. CONDITIONAL:");
Conditional<Integer> isPositive = num -> num > 0;
Conditional<String> containsJava = str -> str.toLowerCase().contains("java");
System.out.println("Is 5 positive? " + isPositive.test(5));
System.out.println("Is -3 positive? " + isPositive.test(-3));
System.out.println("Contains Java? " + containsJava.test("I love Java programming"));
}
// Custom functional interface examples
@FunctionalInterface
interface StringProcessor {
String process(String input);
// Default method for chaining
default StringProcessor andThen(StringProcessor after) {
return input -> after.process(this.process(input));
}
}
@FunctionalInterface
interface Validator<T> {
boolean validate(T value);
// Default method for combining validators
default Validator<T> and(Validator<T> other) {
return value -> this.validate(value) && other.validate(value);
}
default Validator<T> or(Validator<T> other) {
return value -> this.validate(value) || other.validate(value);
}
}
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
// Default method for operation chaining
default Calculator andThen(Calculator next) {
return (a, b) -> next.calculate(this.calculate(a, b), b);
}
}
@FunctionalInterface
interface Transformer<T, R> {
R transform(T input);
// Default method for composition
default <V> Transformer<V, R> compose(Transformer<V, T> before) {
return input -> this.transform(before.transform(input));
}
}
@FunctionalInterface
interface Conditional<T> {
boolean test(T value);
// Default methods for logical operations
default Conditional<T> and(Conditional<T> other) {
return value -> this.test(value) && other.test(value);
}
default Conditional<T> or(Conditional<T> other) {
return value -> this.test(value) || other.test(value);
}
default Conditional<T> negate() {
return value -> !this.test(value);
}
}
}

Output:

=== CUSTOM FUNCTIONAL INTERFACES ===
1. STRING PROCESSOR:
Upper: HELLO
Excited: Wow!
Chained: FUNCTIONAL!
2. VALIDATOR:
Email valid: true
Age valid: true
Age invalid: false
3. CALCULATOR:
5 + 3 = 8
5 * 3 = 15
2 ^ 8 = 256
4. TRANSFORMER:
'Hello' length: 5
42 in binary: 101010
5. CONDITIONAL:
Is 5 positive? true
Is -3 positive? false
Contains Java? true

Example 3: Real-World Practical Applications

import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class RealWorldApplications {
public static void main(String[] args) {
System.out.println("=== REAL-WORLD FUNCTIONAL INTERFACE APPLICATIONS ===");
// Data processing pipeline
System.out.println("\n1. DATA PROCESSING PIPELINE:");
processUserData();
// Validation framework
System.out.println("\n2. VALIDATION FRAMEWORK:");
validateBusinessObjects();
// Event handling system
System.out.println("\n3. EVENT HANDLING SYSTEM:");
simulateEventSystem();
// Configuration system
System.out.println("\n4. CONFIGURATION SYSTEM:");
demonstrateConfiguration();
}
public static void processUserData() {
List<User> users = Arrays.asList(
new User("Alice", 25, "[email protected]"),
new User("Bob", 17, "[email protected]"),
new User("Charlie", 30, "[email protected]"),
new User("Diana", 16, "[email protected]"),
new User("Eve", 22, "[email protected]")
);
// Functional pipeline for data processing
List<String> adultUserEmails = users.stream()
.filter(user -> user.getAge() >= 18)                    // Predicate
.map(User::getEmail)                                    // Function
.map(email -> "EMAIL: " + email.toUpperCase())          // Function  
.collect(Collectors.toList());
System.out.println("Adult user emails:");
adultUserEmails.forEach(System.out::println);              // Consumer
// Calculate average age
Double averageAge = users.stream()
.mapToInt(User::getAge)                                // ToIntFunction
.average()
.orElse(0.0);
System.out.printf("Average age: %.1f%n", averageAge);
// Group users by age category
Map<String, List<User>> usersByCategory = users.stream()
.collect(Collectors.groupingBy(user -> 
user.getAge() < 18 ? "Minor" : 
user.getAge() < 30 ? "Young Adult" : "Adult"));
System.out.println("\nUsers by category:");
usersByCategory.forEach((category, userList) -> {
System.out.println(category + ": " + 
userList.stream().map(User::getName).collect(Collectors.joining(", ")));
});
}
public static void validateBusinessObjects() {
// Validators using functional interfaces
Predicate<String> emailValidator = email -> 
email != null && email.matches("^[\\w.%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$");
Predicate<String> phoneValidator = phone -> 
phone != null && phone.matches("^\\+?[\\d\\s-()]{10,}$");
Predicate<Integer> ageValidator = age -> age != null && age >= 0 && age <= 150;
// Combined validator
Predicate<User> userValidator = user -> 
emailValidator.test(user.getEmail()) && 
ageValidator.test(user.getAge()) &&
user.getName() != null && !user.getName().trim().isEmpty();
List<User> users = Arrays.asList(
new User("Valid User", 25, "[email protected]"),
new User("", 25, "[email protected]"),                 // Invalid name
new User("Invalid Email", 25, "invalid-email"),        // Invalid email
new User("Invalid Age", -5, "[email protected]")       // Invalid age
);
System.out.println("User validation results:");
users.forEach(user -> {
boolean isValid = userValidator.test(user);
System.out.println(user.getName() + ": " + (isValid ? "✅ Valid" : "❌ Invalid"));
});
// Custom validation with messages
Function<User, List<String>> detailedValidator = user -> {
List<String> errors = new ArrayList<>();
if (user.getName() == null || user.getName().trim().isEmpty()) {
errors.add("Name is required");
}
if (!emailValidator.test(user.getEmail())) {
errors.add("Invalid email format");
}
if (!ageValidator.test(user.getAge())) {
errors.add("Age must be between 0 and 150");
}
return errors;
};
System.out.println("\nDetailed validation:");
users.forEach(user -> {
List<String> errors = detailedValidator.apply(user);
if (errors.isEmpty()) {
System.out.println(user.getName() + ": ✅ Valid");
} else {
System.out.println(user.getName() + ": " + String.join(", ", errors));
}
});
}
public static void simulateEventSystem() {
// Event handlers using functional interfaces
Consumer<String> emailNotifier = message -> 
System.out.println("📧 Sending email: " + message);
Consumer<String> smsNotifier = message -> 
System.out.println("📱 Sending SMS: " + message);
Consumer<String> pushNotifier = message -> 
System.out.println("📲 Sending push notification: " + message);
Consumer<String> logger = message -> 
System.out.println("📝 Logging event: " + message);
// Event processor
Function<Event, String> eventProcessor = event -> {
String message = String.format("Event: %s - %s", event.getType(), event.getData());
return message;
};
// Combine handlers
Consumer<String> multiChannelNotifier = emailNotifier
.andThen(smsNotifier)
.andThen(pushNotifier)
.andThen(logger);
// Process events
List<Event> events = Arrays.asList(
new Event("USER_REGISTERED", "Alice joined"),
new Event("ORDER_PLACED", "Order #12345"),
new Event("PAYMENT_RECEIVED", "$100.00")
);
System.out.println("Processing events:");
events.forEach(event -> {
String message = eventProcessor.apply(event);
multiChannelNotifier.accept(message);
System.out.println("---");
});
// Conditional event handling
Predicate<Event> importantEvent = event -> 
event.getType().equals("PAYMENT_RECEIVED") || 
event.getType().equals("SYSTEM_ALERT");
Consumer<Event> importantEventHandler = event -> {
System.out.println("🚨 IMPORTANT: " + event.getType() + " - " + event.getData());
System.out.println("⚠️  Immediate action required!");
};
Consumer<Event> normalEventHandler = event -> 
System.out.println("ℹ️  Normal: " + event.getType() + " - " + event.getData());
// Route events based on importance
Consumer<Event> eventRouter = event -> {
if (importantEvent.test(event)) {
importantEventHandler.accept(event);
} else {
normalEventHandler.accept(event);
}
};
System.out.println("\nEvent routing:");
events.forEach(eventRouter);
}
public static void demonstrateConfiguration() {
// Configuration providers using suppliers
Supplier<String> databaseUrlSupplier = () -> 
System.getenv().getOrDefault("DB_URL", "jdbc:mysql://localhost:3306/app");
Supplier<Integer> maxConnectionsSupplier = () -> 
Integer.parseInt(System.getenv().getOrDefault("MAX_CONNECTIONS", "10"));
Supplier<Boolean> debugModeSupplier = () -> 
Boolean.parseBoolean(System.getenv().getOrDefault("DEBUG_MODE", "false"));
// Configuration loader with fallback
Function<String, Supplier<String>> configLoader = key -> 
() -> System.getenv().getOrDefault(key, "DEFAULT_" + key);
// Lazy configuration evaluation
Supplier<AppConfig> appConfigSupplier = () -> 
new AppConfig(
databaseUrlSupplier.get(),
maxConnectionsSupplier.get(),
debugModeSupplier.get()
);
System.out.println("Application configuration:");
AppConfig config = appConfigSupplier.get();
System.out.println("Database URL: " + config.getDatabaseUrl());
System.out.println("Max Connections: " + config.getMaxConnections());
System.out.println("Debug Mode: " + config.isDebugMode());
// Configuration validator
Predicate<AppConfig> configValidator = cfg -> 
cfg.getDatabaseUrl() != null && 
!cfg.getDatabaseUrl().isEmpty() && 
cfg.getMaxConnections() > 0;
System.out.println("Configuration valid: " + configValidator.test(config));
// Configuration transformer
Function<AppConfig, Map<String, Object>> configToMap = cfg -> {
Map<String, Object> map = new HashMap<>();
map.put("database.url", cfg.getDatabaseUrl());
map.put("database.maxConnections", cfg.getMaxConnections());
map.put("app.debug", cfg.isDebugMode());
return map;
};
System.out.println("\nConfiguration as map:");
Map<String, Object> configMap = configToMap.apply(config);
configMap.forEach((key, value) -> System.out.println(key + " = " + value));
}
}
// Supporting classes
class User {
private String name;
private int age;
private String email;
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
@Override
public String toString() {
return String.format("User{name='%s', age=%d, email='%s'}", name, age, email);
}
}
class Event {
private String type;
private String data;
public Event(String type, String data) {
this.type = type;
this.data = data;
}
public String getType() { return type; }
public String getData() { return data; }
}
class AppConfig {
private String databaseUrl;
private int maxConnections;
private boolean debugMode;
public AppConfig(String databaseUrl, int maxConnections, boolean debugMode) {
this.databaseUrl = databaseUrl;
this.maxConnections = maxConnections;
this.debugMode = debugMode;
}
public String getDatabaseUrl() { return databaseUrl; }
public int getMaxConnections() { return maxConnections; }
public boolean isDebugMode() { return debugMode; }
}

Output:

=== REAL-WORLD FUNCTIONAL INTERFACE APPLICATIONS ===
1. DATA PROCESSING PIPELINE:
Adult user emails:
EMAIL: [email protected]
EMAIL: [email protected]
EMAIL: [email protected]
Average age: 22.0
Users by category:
Young Adult: Alice, Eve
Minor: Bob, Diana
Adult: Charlie
2. VALIDATION FRAMEWORK:
User validation results:
Valid User: ✅ Valid
: ❌ Invalid
Invalid Email: ❌ Invalid
Invalid Age: ❌ Invalid
Detailed validation:
Valid User: ✅ Valid
: Name is required
Invalid Email: Invalid email format
Invalid Age: Age must be between 0 and 150
3. EVENT HANDLING SYSTEM:
Processing events:
📧 Sending email: Event: USER_REGISTERED - Alice joined
📱 Sending SMS: Event: USER_REGISTERED - Alice joined
📲 Sending push notification: Event: USER_REGISTERED - Alice joined
📝 Logging event: Event: USER_REGISTERED - Alice joined
---
📧 Sending email: Event: ORDER_PLACED - Order #12345
📱 Sending SMS: Event: ORDER_PLACED - Order #12345
📲 Sending push notification: Event: ORDER_PLACED - Order #12345
📝 Logging event: Event: ORDER_PLACED - Order #12345
---
📧 Sending email: Event: PAYMENT_RECEIVED - $100.00
📱 Sending SMS: Event: PAYMENT_RECEIVED - $100.00
📲 Sending push notification: Event: PAYMENT_RECEIVED - $100.00
📝 Logging event: Event: PAYMENT_RECEIVED - $100.00
---
Event routing:
ℹ️  Normal: USER_REGISTERED - Alice joined
ℹ️  Normal: ORDER_PLACED - Order #12345
🚨 IMPORTANT: PAYMENT_RECEIVED - $100.00
⚠️  Immediate action required!
4. CONFIGURATION SYSTEM:
Application configuration:
Database URL: jdbc:mysql://localhost:3306/app
Max Connections: 10
Debug Mode: false
Configuration valid: true
Configuration as map:
app.debug = false
database.url = jdbc:mysql://localhost:3306/app
database.maxConnections = 10

Example 4: Advanced Patterns and Method References

import java.util.*;
import java.util.function.*;
import java.util.concurrent.*;
public class AdvancedPatterns {
public static void main(String[] args) {
System.out.println("=== ADVANCED FUNCTIONAL PATTERNS ===");
// Method references
System.out.println("\n1. METHOD REFERENCES:");
demonstrateMethodReferences();
// Constructor references
System.out.println("\n2. CONSTRUCTOR REFERENCES:");
demonstrateConstructorReferences();
// Higher-order functions
System.out.println("\n3. HIGHER-ORDER FUNCTIONS:");
demonstrateHigherOrderFunctions();
// Currying and partial application
System.out.println("\n4. CURRYING AND PARTIAL APPLICATION:");
demonstrateCurrying();
// Functional composition
System.out.println("\n5. FUNCTIONAL COMPOSITION:");
demonstrateComposition();
// Memoization
System.out.println("\n6. MEMOIZATION:");
demonstrateMemoization();
}
public static void demonstrateMethodReferences() {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
// Static method reference
Function<String, Integer> parser = Integer::parseInt;
System.out.println("Parsed: " + parser.apply("42"));
// Instance method reference (bound)
String prefix = "Hello, ";
Function<String, String> greeter = prefix::concat;
System.out.println("Greeting: " + greeter.apply("Alice"));
// Instance method reference (unbound)
Function<String, String> toUpperCase = String::toUpperCase;
System.out.println("Uppercase: " + toUpperCase.apply("hello"));
// Arbitrary object method reference
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
// Method reference to println
Consumer<String> printer = System.out::println;
printer.accept("Method reference to System.out.println");
}
public static void demonstrateConstructorReferences() {
// Constructor references for different scenarios
// String constructor
Function<String, String> stringCopy = String::new;
System.out.println("String copy: " + stringCopy.apply("Original"));
// ArrayList constructor
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> newList = listSupplier.get();
newList.add("First item");
System.out.println("New list: " + newList);
// Array constructor
Function<Integer, String[]> arrayCreator = String[]::new;
String[] stringArray = arrayCreator.apply(3);
stringArray[0] = "A";
stringArray[1] = "B";
stringArray[2] = "C";
System.out.println("Array: " + Arrays.toString(stringArray));
// Custom object constructor
Function<String, Person> personCreator = Person::new;
Person person = personCreator.apply("John Doe");
System.out.println("Created: " + person);
}
public static void demonstrateHigherOrderFunctions() {
// Functions that take functions as parameters or return functions
// Function that returns a function
Function<Integer, Function<Integer, Integer>> adderCreator = x -> y -> x + y;
Function<Integer, Integer> add5 = adderCreator.apply(5);
System.out.println("5 + 3 = " + add5.apply(3));
System.out.println("5 + 10 = " + add5.apply(10));
// Function that takes a function as parameter
Function<Function<Integer, Integer>, Integer> applyToTen = func -> func.apply(10);
System.out.println("Square of 10: " + applyToTen.apply(x -> x * x));
System.out.println("10 + 5: " + applyToTen.apply(x -> x + 5));
// Conditional function application
BiFunction<Predicate<Integer>, Function<Integer, String>, Function<Integer, String>> 
conditionalApplier = (predicate, function) -> 
input -> predicate.test(input) ? function.apply(input) : "Invalid";
Function<Integer, String> evenToString = conditionalApplier.apply(
n -> n % 2 == 0, 
n -> "Even: " + n
);
System.out.println("Even check 4: " + evenToString.apply(4));
System.out.println("Even check 3: " + evenToString.apply(3));
}
public static void demonstrateCurrying() {
// Currying: Transforming multi-argument function into sequence of single-argument functions
// Regular function
BiFunction<Integer, Integer, Integer> regularAdd = (a, b) -> a + b;
System.out.println("Regular add: " + regularAdd.apply(5, 3));
// Curried version
Function<Integer, Function<Integer, Integer>> curriedAdd = a -> b -> a + b;
System.out.println("Curried add: " + curriedAdd.apply(5).apply(3));
// Partial application
Function<Integer, Integer> addFive = curriedAdd.apply(5);
System.out.println("Add five to 3: " + addFive.apply(3));
System.out.println("Add five to 10: " + addFive.apply(10));
// Three-argument currying
Function<Integer, Function<Integer, Function<Integer, Integer>>> 
curriedThree = a -> b -> c -> a + b + c;
Function<Integer, Integer> addFiveAndThree = curriedThree.apply(5).apply(3);
System.out.println("5 + 3 + 2 = " + addFiveAndThree.apply(2));
System.out.println("5 + 3 + 7 = " + addFiveAndThree.apply(7));
// Practical example: Discount calculator
Function<Double, Function<Double, Function<Double, Double>>> 
discountCalculator = basePrice -> discountRate -> taxRate -> 
basePrice * (1 - discountRate) * (1 + taxRate);
Function<Double, Double> tenPercentOff = discountCalculator.apply(100.0).apply(0.10);
System.out.println("$100 with 10% off + 8% tax: $" + tenPercentOff.apply(0.08));
}
public static void demonstrateComposition() {
// Function composition: Combining multiple functions
Function<Integer, Integer> timesTwo = x -> x * 2;
Function<Integer, Integer> plusThree = x -> x + 3;
Function<Integer, String> toString = Object::toString;
// Compose: f(g(x)) - right to left
Function<Integer, Integer> timesTwoPlusThree = plusThree.compose(timesTwo);
System.out.println("2 * 5 + 3 = " + timesTwoPlusThree.apply(5));
// AndThen: g(f(x)) - left to right  
Function<Integer, Integer> plusThreeTimesTwo = timesTwo.andThen(plusThree);
System.out.println("(5 + 3) * 2 = " + plusThreeTimesTwo.apply(5));
// Complex composition
Function<Integer, String> complex = timesTwo
.andThen(plusThree)
.andThen(toString)
.andThen(str -> "Result: " + str);
System.out.println("Complex: " + complex.apply(5));
// Predicate composition
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
System.out.println("4 is even and positive: " + isEvenAndPositive.test(4));
System.out.println("-2 is even and positive: " + isEvenAndPositive.test(-2));
System.out.println("3 is even and positive: " + isEvenAndPositive.test(3));
// Consumer composition
Consumer<String> print = System.out::print;
Consumer<String> println = System.out::println;
Consumer<String> printWithSpace = s -> System.out.print(s + " ");
Consumer<String> complexPrint = printWithSpace.andThen(println);
complexPrint.accept("Hello");
complexPrint.accept("World");
}
public static void demonstrateMemoization() {
// Memoization: Caching function results
Function<Integer, Integer> expensiveOperation = n -> {
System.out.println("Computing for: " + n);
try { Thread.sleep(1000); } catch (InterruptedException e) { }
return n * n;
};
// Memoized version
Function<Integer, Integer> memoized = memoize(expensiveOperation);
System.out.println("First call (should compute):");
long start = System.currentTimeMillis();
System.out.println("Result: " + memoized.apply(5));
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start) + "ms");
System.out.println("\nSecond call (should use cache):");
start = System.currentTimeMillis();
System.out.println("Result: " + memoized.apply(5));
end = System.currentTimeMillis();
System.out.println("Time: " + (end - start) + "ms");
System.out.println("\nDifferent input (should compute):");
start = System.currentTimeMillis();
System.out.println("Result: " + memoized.apply(10));
end = System.currentTimeMillis();
System.out.println("Time: " + (end - start) + "ms");
}
// Memoization utility function
private static <T, R> Function<T, R> memoize(Function<T, R> function) {
Map<T, R> cache = new ConcurrentHashMap<>();
return input -> cache.computeIfAbsent(input, function);
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() { return name; }
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
}

Output:

=== ADVANCED FUNCTIONAL PATTERNS ===
1. METHOD REFERENCES:
Parsed: 42
Greeting: Hello, Alice
Uppercase: HELLO
ALICE
BOB
CHARLIE
DIANA
Method reference to System.out.println
2. CONSTRUCTOR REFERENCES:
String copy: Original
New list: [First item]
Array: [A, B, C]
Created: Person{name='John Doe'}
3. HIGHER-ORDER FUNCTIONS:
5 + 3 = 8
5 + 10 = 15
Square of 10: 100
10 + 5: 15
Even check 4: Even: 4
Even check 3: Invalid
4. CURRYING AND PARTIAL APPLICATION:
Regular add: 8
Curried add: 8
Add five to 3: 8
Add five to 10: 15
5 + 3 + 2 = 10
5 + 3 + 7 = 15
$100 with 10% off + 8% tax: $97.2
5. FUNCTIONAL COMPOSITION:
2 * 5 + 3 = 13
(5 + 3) * 2 = 16
Complex: Result: 13
4 is even and positive: true
-2 is even and positive: false
3 is even and positive: false
Hello 
World 
6. MEMOIZATION:
First call (should compute):
Computing for: 5
Result: 25
Time: 1001ms
Second call (should use cache):
Result: 25
Time: 0ms
Different input (should compute):
Computing for: 10
Result: 100
Time: 1001ms

When to Use Functional Interfaces

✅ Appropriate Uses:

  • Behavior parameterization: Passing different behaviors as parameters
  • Callback mechanisms: Event handlers, observers
  • Stream operations: Filter, map, reduce operations
  • Strategy pattern: Different algorithms for the same operation
  • Factory methods: Creating objects with different strategies

❌ Avoid When:

  • Complex multi-method contracts: Use regular interfaces
  • Stateful operations: When behavior depends on mutable state
  • Performance-critical code: Lambda overhead might be significant
  • Complex exception handling: Lambda exception handling can be tricky

Best Practices

  1. Use @FunctionalInterface annotation for documentation
  2. Prefer built-in interfaces when they fit your needs
  3. Keep lambdas short and focused on single responsibility
  4. Use method references when they improve readability
  5. Consider performance implications for hot code paths
  6. Document functional parameters with meaningful names
  7. Use type inference but avoid overly complex expressions

Common Patterns

// Pattern 1: Strategy pattern
public class Processor {
private final Function<String, String> strategy;
public Processor(Function<String, String> strategy) {
this.strategy = strategy;
}
public String process(String input) {
return strategy.apply(input);
}
}
// Pattern 2: Builder with functional configuration
public class Configurator {
private Consumer<String> logger = System.out::println;
private Predicate<String> validator = s -> true;
public Configurator withLogger(Consumer<String> logger) {
this.logger = logger;
return this;
}
public Configurator withValidator(Predicate<String> validator) {
this.validator = validator;
return this;
}
}
// Pattern 3: Template method with functional hooks
public class Template {
public void execute(Supplier<Data> dataSupplier, 
Function<Data, Result> processor,
Consumer<Result> resultHandler) {
Data data = dataSupplier.get();
Result result = processor.apply(data);
resultHandler.accept(result);
}
}

Conclusion

Functional interfaces are Java's gateway to functional programming:

  • Single method contracts: Define precise behavior units
  • Lambda compatibility: Enable concise anonymous implementations
  • Method reference support: Reuse existing methods as implementations
  • Built-in utilities: Common patterns provided by Java
  • Composition support: Combine and chain behaviors

Key Takeaways:

  • @FunctionalInterface ensures single abstract method
  • Lambdas provide concise implementations
  • Method references reuse existing code
  • Built-in interfaces cover common use cases
  • Functional composition enables powerful data processing

Functional interfaces transform Java from a purely object-oriented language into a hybrid language that combines the best of both object-oriented and functional programming paradigms, making code more expressive, reusable, and maintainable!

Leave a Reply

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


Macro Nepal Helper