1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Functional interface = interface with exactly one abstract method (SAM). @FunctionalInterface annotation (optional, recommended). Lambda target type—defines what lambda does. Built-in (java.util.function): Predicate (test→boolean), Function<T,R> (apply→transform), Consumer (accept→side effect), Supplier (get→provide value), UnaryOperator (same type I/O), BinaryOperator (combine two).
- Combinable: and(), or(), compose(), andThen().
- Primitive variants: IntPredicate, LongFunction (avoid boxing)!
Think of electrical outlet. Functional interface = outlet shape (single socket). Lambda = plug (fits the socket). Predicate = quality inspector (pass/fail). Function = assembly machine (input→output). Consumer = shredder (input, no output). Supplier = vending machine (no input, output)!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy: Tool Interfaces
- Predicate: Quality control gate ("Is this acceptable? Yes/No")
- Function: Transformer ("Convert this to that")
- Consumer: Sink ("Process this, no return")
- Supplier: Source ("Give me something, no input needed")
Functional Interface Hierarchy
graph TD
A[Functional Interfaces] --> B[Predicate\u003cT\u003e]
A --> C[Function\u003cT,R\u003e]
A --> D[Consumer\u003cT\u003e]
A --> E[Supplier\u003cT\u003e]
C --> F[UnaryOperator\u003cT\u003e]
C --> G[BinaryOperator\u003cT\u003e]
B -->|test→boolean| H[Filtering]
C -->|apply→R| I[Transformation]
D -->|accept→void| J[Side Effects]
E -->|get→T| K[Lazy Init]
3. Technical Mastery (The "Deep Dive")
Core Functional Interfaces
| Interface | Method | Input | Output | Use For |
|---|---|---|---|---|
| Predicate | test(T) | T | boolean | Filtering |
| Function<T,R> | apply(T) | T | R | Mapping |
| Consumer | accept(T) | T | void | forEach |
| Supplier | get() | none | T | Factories |
| UnaryOperator | apply(T) | T | T | Transform same type |
| BinaryOperator | apply(T,T) | T, T | T | Reduce |
| BiFunction<T,U,R> | apply(T,U) | T, U | R | Two inputs |
4. Interactive & Applied Code
java
import java.util.*;
import java.util.function.*;
public class FunctionalInterfacesDemo {
public static void main(String[] args) {
demonstratePredicate();
demonstrateFunction();
demonstrateConsumer();
demonstrateSupplier();
demonstrateOperators();
demonstrateCombining();
}
// Predicate<T>: T -> boolean (filtering)
static void demonstratePredicate() {
System.out.println("=== PREDICATE ===");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> greaterThan3 = n -> n > 3;
System.out.println("Is 4 even? " + isEven.test(4));
// Combining predicates
Predicate<Integer> evenAndGreater = isEven.and(greaterThan3);
System.out.println("Even AND >3:");
numbers.stream().filter(evenAndGreater).forEach(System.out::println);
// Negate
Predicate<Integer> isOdd = isEven.negate();
System.out.println("Odd numbers:");
numbers.stream().filter(isOdd).forEach(System.out::println);
}
// Function<T, R>: T -> R (transformation)
static void demonstrateFunction() {
System.out.println("\n=== FUNCTION ===");
Function<String, Integer> length = s -> s.length();
Function<Integer, Integer> square = n -> n * n;
Function<String, String> upper = String::toUpperCase;
System.out.println("Length of 'hello': " + length.apply("hello"));
// Chaining functions
Function<String, Integer> lengthSquared = length.andThen(square);
System.out.println("Length squared of 'abc': " + lengthSquared.apply("abc"));
// Compose (reverse order)
Function<Integer, String> repeat = n -> "x".repeat(n);
Function<String, String> upperRepeat = upper.compose(repeat);
System.out.println("Upper compose repeat: " + upperRepeat.apply("hello"));
}
// Consumer<T>: T -> void (side effects)
static void demonstrateConsumer() {
System.out.println("\n=== CONSUMER ===");
List<String> names = Arrays.asList("Alice", "Bob", "Carol");
Consumer<String> print = s -> System.out.println("Name: " + s);
Consumer<String> logLength = s -> System.out.println("Length: " + s.length());
// Single consumer
names.forEach(print);
// Chaining consumers
Consumer<String> printAndLog = print.andThen(logLength);
System.out.println("\nChained:");
names.forEach(printAndLog);
}
// Supplier<T>: () -> T (provide values)
static void demonstrateSupplier() {
System.out.println("\n=== SUPPLIER ===");
Supplier<Double> randomSupplier = () -> Math.random();
Supplier<List<String>> listSupplier = () -> new ArrayList<>();
Supplier<String> uuidSupplier = () -> UUID.randomUUID().toString();
System.out.println("Random: " + randomSupplier.get());
System.out.println("New list: " + listSupplier.get());
System.out.println("UUID: " + uuidSupplier.get());
// Lazy initialization
System.out.println("Creating expensive object...");
String value = getOrCreate(null, () -> {
System.out.println(" Computing expensive value...");
return "Expensive Result";
});
System.out.println("Value: " + value);
}
static String getOrCreate(String cached, Supplier<String> supplier) {
return cached != null ? cached : supplier.get();
}
// UnaryOperator and BinaryOperator
static void demonstrateOperators() {
System.out.println("\n=== OPERATORS ===");
// UnaryOperator<T>: T -> T (same type)
UnaryOperator<Integer> increment = n -> n + 1;
UnaryOperator<String> wrapBrackets = s -> "[" + s + "]";
System.out.println("Increment 5: " + increment.apply(5));
System.out.println("Wrap 'hello': " + wrapBrackets.apply("hello"));
// BinaryOperator<T>: (T, T) -> T (combine two)
BinaryOperator<Integer> add = (a, b) -> a + b;
BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
BinaryOperator<String> concat = (a, b) -> a + b;
System.out.println("5 + 3 = " + add.apply(5, 3));
System.out.println("max(5, 3) = " + max.apply(5, 3));
System.out.println("Concat: " + concat.apply("Hello", "World"));
}
// Advanced: Combining functions
static void demonstrateCombining() {
System.out.println("\n=== COMBINING ===");
List<String> words = Arrays.asList("apple", "banana", "cherry");
// Predicate: Filter long words
Predicate<String> isLong = s -> s.length() > 5;
// Function: Transform to uppercase
Function<String, String> upper = String::toUpperCase;
// Consumer: Print with prefix
Consumer<String> printWithPrefix = s -> System.out.println("=> " + s);
// Combine all
words.stream()
.filter(isLong) // Predicate
.map(upper) // Function
.forEach(printWithPrefix); // Consumer
}
}
// BiFunction, BiConsumer, BiPredicate
class BiFunctionalInterfacesDemo {
public static void main(String[] args) {
// BiFunction<T, U, R>: (T, U) -> R
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println("3 * 4 = " + multiply.apply(3, 4));
// BiConsumer<T, U>: (T, U) -> void
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
BiConsumer<String, Integer> printer = (k, v) ->
System.out.println(k + " is " + v + " years old");
map.forEach(printer);
// BiPredicate<T, U>: (T, U) -> boolean
BiPredicate<String, Integer> lengthMatches = (s, n) -> s.length() == n;
System.out.println("'hello' has length 5? " + lengthMatches.test("hello", 5));
}
}5. The Comparison & Decision Layer
| Scenario | Use This | Example |
|---|---|---|
| Test condition | Predicate | s -> s.isEmpty() |
| Transform | Function | s -> s.length() |
| Side effect | Consumer | s -> System.out.println(s) |
| Provide value | Supplier | () -> new ArrayList<>() |
| Same type I/O | UnaryOperator | n -> n * 2 |
| Combine two | BinaryOperator | (a,b) -> a + b |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Why use Predicate instead of just boolean lambda?" Answer: Predicate provides combinator methods (and/or/negate)!
java
// ❌ Just boolean lambda (no combinators)
List<Integer> filtered = list.stream()
.filter(n -> n > 0 && n % 2 == 0) // Complex inline
.collect(Collectors.toList());
// ✅ Predicate with combinators (readable, reusable)
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;
List<Integer> filtered = list.stream()
.filter(isPositive.and(isEven)) // Clear, composable!
.collect(Collectors.toList());Pro-Tip: Primitive functional interfaces (avoid boxing):
java
// ❌ SLOW: Boxing (Integer wrapping)
Predicate<Integer> isEven = n -> n % 2 == 0;
// For 1M elements: Wraps 1M ints → Integers (slow!)
// ✅ FAST: No boxing
IntPredicate isEven = n -> n % 2 == 0;
// Works directly with primitive ints (fast!)
// Other primitives:
IntFunction, LongConsumer, DoubleSupplier, etc.