Functional Interfaces Recap
Functional Interfaces Recap
Before lambdas can make sense, you need a firm grip on functional interfaces — the type system that makes lambdas possible in Java. This lesson drills into what a functional interface really is, why the JVM needs it, and how @FunctionalInterface keeps your design honest.
What is a Functional Interface?
A functional interface is any interface that has exactly one abstract method. That single abstract method (abbreviated SAM) is the "shape" the lambda must fill. The interface can have any number of default methods, static methods, or methods inherited from java.lang.Object — only abstract methods count toward the SAM constraint.
Because Greeter has exactly one abstract method, the compiler can map any lambda name -> "Hello, " + name directly onto it without ambiguity.
The @FunctionalInterface Annotation
Java 8 introduced the @FunctionalInterface annotation. It does two things:
- Documents intent — it signals to every reader that this interface is meant to be used as a lambda target.
- Enforces the SAM constraint at compile time — if you accidentally add a second abstract method, the compiler rejects the class rather than silently breaking every lambda that used it.
@FunctionalInterface — any SAM interface can accept a lambda. The annotation is a compile-time guard that makes your contract explicit. Always add it to interfaces you intend to use as lambdas.
Why Java Needs a Type for Lambdas
Unlike languages such as Python or JavaScript, Java is statically typed: every expression must have a known type at compile time. A lambda on its own — x -> x * 2 — has no standalone type. The compiler infers the type from the target context: the functional interface it is being assigned to or passed as.
The same lambda expression can be assigned to different functional interfaces as long as the method signatures match:
java.util.function.*) over rolling your own. The JDK ships 43 ready-made functional interfaces — Predicate, Function, Consumer, Supplier, and their primitive variants — which you will explore in the upcoming lessons. Defining a custom @FunctionalInterface makes sense only when you need a more descriptive name or a checked-exception signature.
What Breaks the SAM Rule
Adding a second abstract method immediately turns the interface into an ordinary interface — it can no longer be used as a lambda target:
The compiler message is clear: "Invalid @FunctionalInterface annotation; Broken is not a functional interface." This is the annotation earning its keep.
Abstract Methods from Object Do Not Count
Every Java class ultimately inherits from Object, so equals, hashCode, and toString are always available. An interface can redeclare these without violating the SAM rule:
Object-redeclarations do not break the SAM constraint. Only additional non-default, non-Object abstract methods do.
A Quick Checklist
- Exactly one abstract method — the SAM. All lambdas and method references targeting this interface must match its signature.
- Any number of default and static methods — these add behaviour without breaking the contract.
@FunctionalInterface— documents intent and gives a compile-time safety net.- The annotation is not required by the compiler, but it is a best practice you should follow consistently.
Summary
A functional interface is the bridge between Java's static type system and the world of lambdas. Its single abstract method defines the shape every lambda assigned to it must match. The @FunctionalInterface annotation locks that contract in place, turning an accidental second abstract method into a compile-time error rather than a runtime surprise. With this foundation solid, the next lesson moves on to Predicate<T> — the JDK's first-class functional interface for boolean tests.