Detailed explanation
A functional interface in Java is an interface with one abstract method contract, often called a Single Abstract Method (SAM) interface. It represents one operation that can be supplied as behaviour rather than written inside a separate named class. Functional interfaces are the target types for lambda expressions and method references, introduced in Java 8. They matter because they make callbacks, stream operations, sorting rules, and task execution concise while retaining compile-time type checking.
The @FunctionalInterface annotation is optional: an eligible interface remains functional without it. However, the annotation asks the compiler to reject the declaration if it does not satisfy the functional-interface rules, documenting intent and protecting against accidental changes. Default and static methods do not count towards the abstract-method requirement; neither do private methods, supported in interfaces since Java 9. Abstract declarations matching public methods of Object, such as equals(Object), are also excluded. Inherited abstract methods count, although compatible inherited declarations can represent the same single method contract.
For example, declare @FunctionalInterface interface Calculator { int operate(int a, int b); }. Then Calculator add = (a, b) -> a + b; supplies the implementation of operate, and add.operate(2, 3) returns 5. The assignment provides the target type, so the compiler checks that the lambda accepts two int arguments and produces an int result. A lambda does not introduce an independently named method; it supplies behaviour for the interface’s abstract method. Java typically implements lambda creation through invokedynamic machinery, rather than simply compiling every lambda into an anonymous inner class.
Common examples include Runnable, whose run method returns nothing; Callable<V>, whose call method returns a value and can throw checked exceptions; and Comparator<T>, whose compare method defines ordering. In java.util.function, Function<T, R> transforms a value, Predicate<T> tests a condition, Consumer<T> accepts a value without returning a result, and Supplier<T> produces a value without accepting an argument. Interviewers usually expect the definition, a working lambda example, and the distinction between abstract and default methods. Stronger answers also explain target typing and why Comparator remains functional despite declaring equals.
Key points to remember
- A functional interface has one abstract method contract and can be implemented by a lambda or method reference.
- @FunctionalInterface is optional but enables compiler validation of the interface’s intent.
- Default, static, and private methods do not count towards the abstract-method requirement.
- Abstract declarations matching public Object methods are excluded; inherited abstract contracts must still be considered.
- Standard examples include Runnable, Callable, Comparator, Function, Predicate, Consumer, and Supplier.
Common mistakes candidates make
- Saying a functional interface can contain only one method in total, ignoring default, static, and private methods.
- Claiming @FunctionalInterface is required for lambda compatibility.
- Counting Comparator.equals(Object) as a second qualifying abstract method and incorrectly concluding that Comparator is not functional.
Likely follow-up questions
How is a lambda different from an anonymous inner class?
A lambda implements a functional-interface contract, whereas an anonymous inner class can also extend a class or implement a non-functional interface. Inside a lambda, this refers to the enclosing instance; inside an anonymous class, it refers to that anonymous-class instance.
Can a functional interface extend another interface?
Yes, provided its resulting abstract methods still form one qualifying method contract. Adding an unrelated inherited abstract method makes it non-functional, while compatible declarations of the same method can preserve functional status.
Can a lambda throw a checked exception?
Yes, if the target interface’s abstract method declares a compatible checked exception. For example, Callable.call allows Exception, but Runnable.run does not, so a Runnable lambda must handle checked exceptions internally.
Interview tip: State the SAM definition, demonstrate the Calculator lambda, and then mention optional annotation validation and the methods excluded from the count.
