Module Summary — Working with Functional Interfaces
Functional interfaces are the bridge between Java’s object-oriented type system and functional programming with lambdas.
Let’s bring the key concepts from this module together.
1. What Is a Functional Interface?
A functional interface has exactly one abstract method.
@FunctionalInterface
interface ProductFilter {
boolean accept(Product product);
}The key rule:
Functional Interface
│
▼
Exactly ONE abstract method
│
├── default methods ✅
├── static methods ✅
└── Object methods ✅Methods matching public methods from java.lang.Object, such as equals(), don't count toward the single-abstract-method requirement.
2. @FunctionalInterface
The annotation:
@FunctionalInterfaceis optional.
An interface with exactly one abstract method is functional even without it.
So why use it?
@FunctionalInterface
│
├── Documents intent
│
└── Enables compiler validationIf someone accidentally adds another abstract method, the compiler reports an error.
Think of it similarly to @Override: optional, but useful for expressing intent and catching mistakes.
3. Standard Functional Interfaces
Java provides reusable functional interfaces in:
java.util.functionInstead of memorizing them individually, think about their shape:
Inputs → OutputThe Core Four
Function<T,R>
T ─────────────► R
Transform
Consumer<T>
T ─────────────► void
Do something
Supplier<T>
() ────────────► T
Produce
Predicate<T>
T ─────────────► boolean
TestA simple memory trick:
Function → TRANSFORM
Consumer → DO
Supplier → PROVIDE
Predicate → TEST4. Operators
Operators are specialized forms of Function where the input and output types are the same.
UnaryOperator<T>
T ───► TEquivalent in shape to:
Function<T, T>Example:
UnaryOperator<Integer> doubleIt = n -> n * 2;BinaryOperator<T>
T ──┐
├──► T
T ──┘Equivalent in shape to:
BiFunction<T, T, T>Example:
BinaryOperator<Integer> add = (a, b) -> a + b;5. The Bi Family
The prefix:
Bimeans two inputs.
Therefore:
Notice there is no BiSupplier.
A Supplier has no inputs by definition:
() → T6. Functional Interfaces in Real Java APIs
These interfaces appear throughout Java’s standard APIs.
For example:
products.stream()
.filter(product -> product.getPrice() < 10)
.map(Product::getName)
.forEach(System.out::println);We can understand this entirely in terms of functional interfaces:
List<Product>
│
▼
filter()
Predicate<Product>
Product → boolean
│
▼
map()
Function<Product,String>
Product → String
│
▼
forEach()
Consumer<String>
String → voidThis mental model becomes extremely useful when working with Streams.
7. Functional Composition
Small functions can be combined into larger functions.
Get Suraj’s stories in your inbox
Join Medium for free to get updates from this writer.
For example:
Function<Product, BigDecimal> productToPrice = Product::getPrice;
Function<BigDecimal, String> priceToMessage = price -> "Price: " + price;We can combine them:
Function<Product, String> productToMessage =
productToPrice.andThen(priceToMessage);Visualized:
Product
│
▼
productToPrice
│
▼
BigDecimal
│
▼
priceToMessage
│
▼
StringUseful Function composition methods include:
andThen()
compose()8. Predicate Composition
Predicates can also be combined.
Suppose we have:
Predicate<Product> isFood = ...;
Predicate<Product> isCheap = ...;We can create:
Predicate<Product> isFoodAndCheap = isFood.and(isCheap);Java provides:
and() → A && B
or() → A || B
negate() → !AThis is especially useful for building dynamic rules:
Food
AND
Cheap
AND
Available
AND
In Stock
│
▼
Combined PredicateFunctional composition encourages us to build small reusable behaviors and combine them when needed.
9. Specialized Functional Interfaces
Java generics don’t work directly with primitive types.
For example:
BinaryOperator<Integer>works with wrapper objects:
(Integer, Integer) → IntegerIf we’re actually processing primitive int values, this can involve boxing and unboxing:
int ↔ IntegerJava therefore provides specialized functional interfaces.
Instead of:
BinaryOperator<Integer>we can use:
IntBinaryOperatorwhich works directly with:
(int, int) → int10. Primitive Families
The main specialized interfaces target:
int
long
doubleExamples:
Don’t try to memorize every specialized interface.
Learn to decode the name:
LongToDoubleFunction
│ │
│ └── Output: double
│
└────────── Input: long
long → doubleThe Complete Mental Model
Everything in this module can be connected like this:
FUNCTIONAL INTERFACE
│
▼
One Abstract Method
│
▼
Lambda / Method Reference
│
▼
Behavior as Data
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Function Predicate Consumer
T → R T → boolean T → void
│ │ │
└──────────────┼──────────────┘
│
▼
Functional APIs
│
┌──────────┴──────────┐
▼ ▼
Composition StreamsAnd when primitives are involved:
Generic Functional Interface
│
▼
Wrapper Types
│
boxing/unboxing
│
▼
Primitive Specialization
│
▼
Int / Long / Double interfacesQuick Revision Cheat Sheet
Functional Interface
→ Exactly one abstract method
@FunctionalInterface
→ Optional + compiler validation
Function<T,R>
→ T → R
→ Transform
Consumer<T>
→ T → void
→ Perform
Supplier<T>
→ () → T
→ Produce
Predicate<T>
→ T → boolean
→ Test
UnaryOperator<T>
→ T → T
BiFunction<T,U,R>
→ (T,U) → R
BiConsumer<T,U>
→ (T,U) → void
BiPredicate<T,U>
→ (T,U) → boolean
BinaryOperator<T>
→ (T,T) → T
Functional Composition
→ Combine small functions
Primitive Specialization
→ Avoid unnecessary boxing/unboxingFinal Takeaway
Don’t approach java.util.function as a collection of interfaces to memorize.
Instead, ask:
How many inputs?
+
What is the output?
↓
Recognize the function shape
↓
Choose the interfaceOnce you understand the shape of behavior, Function, Predicate, Consumer, Supplier, operators, Bi... variants, and primitive specializations become much easier to recognize.
This foundation becomes even more valuable in the next topic: the Java Stream API, where functional interfaces, lambda expressions, and method references come together to build expressive data-processing pipelines.









