Definition
- @FunctionalInterface tells the compiler to enforce exactly one abstract method.
- A custom
boolean test(Employee e, double threshold)is a domain-specific two-argument predicate. - Named interfaces like
SalaryCheckare clearer than genericBiPredicate. - The annotation is a safety net: optional for lambdas but prevents accidental contract-breaking.
- Create custom interfaces for readability, primitive avoidance, or domain naming.
Priya at the National Bank of India invents her own yes-or-no form that takes an employee and a threshold amount. Because the form has exactly one question to answer, she can fill it with a one-line lambda.
Understanding the Concepts
How do you write a custom functional interface?
Declare an interface with one abstract method and annotate it.
package com.bank.hr;
@FunctionalInterface
public interface SalaryCheck {
boolean test(Employee e, double threshold);
}
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
public class CustomFIDemo {
public static void main(String[] args) {
SalaryCheck earnsMoreThan = (e, t) -> e.getSalaryAsDouble() > t;
List<Employee> employees = List.of(
new Employee("Rajesh", "Loans", 75000.0),
new Employee("Priya", "IT", 82000.0),
new Employee("Amit", "IT", 45000.0)
);
List<Employee> highEarners = employees.stream()
.filter(e -> earnsMoreThan.test(e, 50_000))
.collect(Collectors.toList());
highEarners.forEach(e -> System.out.println(e.getName()));
}
}
Rajesh
Priya
Why not use BiPredicate?
| Feature | SalaryCheck | BiPredicate
|---|---|---|
| Name | Domain-specific | Generic |
| Primitive boxing | None (primitive double) | Autoboxes double to Double |
| Readability | SalaryCheck.test(e, 50000) | BiPredicate.test(e, 50000.0) |
A named custom interface documents intent and avoids the boxing overhead of Double.
What does @FunctionalInterface enforce?
// CORRECT: exactly one abstract method
@FunctionalInterface
interface SalaryCheck {
boolean test(Employee e, double threshold);
}
// WRONG: two abstract methods → compile error
@FunctionalInterface
interface BrokenCheck {
boolean test(Employee e, double threshold);
String describe(); // second abstract method!
}
Default and static methods are fine; they do not count as abstract.
@FunctionalInterface
interface SalaryCheck {
boolean test(Employee e, double threshold);
default SalaryCheck negate() {
return (e, t) -> !this.test(e, t);
}
}
When should you create a custom interface vs reuse built-ins?
| Create custom when | Reuse built-in when |
|---|---|
| The interface needs a domain-specific name | A standard name like Predicate suffices |
| Primitive parameters avoid boxing overhead | All parameters are objects |
| The interface needs default/static methods | No extra methods needed |
What are common mistakes?
Mistake: omitting the annotation and accidentally adding a second abstract method later.
// Without @FunctionalInterface, someone could add a second method
// and break all existing lambdas. The annotation prevents this.
What should a senior engineer know?
- @FunctionalInterface is a safety net, not a requirement. Lambdas work on any single-abstract-method interface.
- Custom interfaces shine for domain DSLs: SalaryCheck, TransferValidator, LoanEligibility.
- Keep the standard set (Predicate, Function, Consumer, Supplier) as the default choice.
- Adding default methods to a custom functional interface provides composition (negate, and, or) without breaking the single-method contract.
What are common interview follow-up questions?
- "Is @FunctionalInterface required for a lambda?" No, but it enforces the contract.
- "Can a functional interface have default methods?" Yes, as many as needed.
- "Can a functional interface extend another interface?" Yes, as long as the total has exactly one abstract method.
Visual Overview
@FunctionalInterface
SalaryCheck { boolean test(Employee, double) }
│
│ Lambda: (e, t) -> e.getSalary() > t
▼
SalaryCheck earnsMoreThan = (e, t) -> salary > t;
Usage:
earnsMoreThan.test(Rajesh, 50000) → 75000 > 50000 = true
earnsMoreThan.test(Amit, 50000) → 45000 > 50000 = false
Key Takeaway
A @FunctionalInterface with one abstract method can be implemented by a lambda. When the built-in interfaces do not fit your domain, declare a custom one like SalaryCheck with boolean test(Employee, double), which gives a domain-specific name and avoids autoboxing the primitive threshold.
The annotation tells the compiler to enforce the single-method contract, preventing someone from later adding a second abstract method and breaking all existing lambdas. At the National Bank of India, SalaryCheck was clearer than BiPredicate
Create custom functional interfaces when they improve readability, avoid boxing, or deserve a domain-specific name. Otherwise reuse Predicate, Function, and the standard set.
Remember one sentence: @FunctionalInterface enforces one abstract method and enables lambda implementation; create a custom one when the standard interfaces lack domain clarity or force boxing.
---