1. Introduction
A functional interface is an interface that contains exactly one abstract method (that is, a method without an implementation). Only such an interface can be used for a concise method implementation — in Java this is done with lambda expressions (we will study them later).
Why only one method?
Because a functional interface describes exactly one operation. If it had two or more methods, it would be unclear which method must be implemented. So the rule is simple: one interface — one abstract method.
Examples from the standard library
- Runnable — for tasks in threads (void run())
- Callable<V> — for tasks that return a result (V call())
- Comparator<T> — for comparing objects (int compare(T o1, T o2))
- Consumer<T> — a “consumer” of a value (void accept(T t))
- Supplier<T> — a “supplier” of a value (T get())
- Function<T, R> — a function from T to R (R apply(T t))
- Predicate<T> — a condition check (boolean test(T t))
Here is the Runnable interface, for example:
public interface Runnable {
void run();
}
And here is the Comparator interface:
public interface Comparator<T> {
int compare(T o1, T o2);
// ... there are also default and static methods, but there is only one abstract method!
}
Important: default and static methods are not considered abstract, so there can be any number of them!
2. Annotation @FunctionalInterface
Java is strict and principled. To avoid confusion, it lets you explicitly mark an interface as functional using the @FunctionalInterface annotation. It’s like a sticker: “Works with only one button!” — so nobody adds extra stuff.
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Now, if you suddenly forget and add a second abstract method, the compiler will immediately report an error:
@FunctionalInterface
public interface Oops {
void doIt();
void doSomethingElse(); // Error! Two abstract methods
}
Is the annotation mandatory?
No, it is not required. An interface will still be functional without it if it contains exactly one abstract method. But with the annotation you clearly show your intent and protect yourself from accidental mistakes.
Can you add default and static methods?
Yes, you can! The key is to have only one abstract method. All other methods can be default or static, as many as you like.
Example:
@FunctionalInterface
public interface FancyOperation {
int apply(int a, int b);
default void printInfo() {
System.out.println("I am a fancy operation!");
}
static void description() {
System.out.println("A functional interface for arithmetic.");
}
}
3. Examples of declaring and using
Suppose you want to describe an operation on two numbers. Here is how to do it:
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Now you can implement this interface in different ways.
Implementation via a regular class
public class SumOperation implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
Usage:
Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5
Implementation via an anonymous class
Operation multiply = new Operation() {
@Override
public int apply(int a, int b) {
return a * b;
}
};
System.out.println(multiply.apply(2, 3)); // 6
A note about lambdas
Starting with Java 8, such interfaces are conveniently implemented using lambda expressions — a more concise syntax. We will study lambdas in a couple of lectures, so for now it is enough to know that functional interfaces exist specifically to make working with them as convenient as possible.
4. Practice: write your own functional interface
Task 1. Make your own Action!
Create an Action interface that takes a string and returns nothing. Implement it via an anonymous class that prints the string in uppercase.
@FunctionalInterface
interface Action {
void act(String s);
}
public class ActionDemo {
public static void main(String[] args) {
Action shout = new Action() {
@Override
public void act(String text) {
System.out.println(text.toUpperCase());
}
};
shout.act("i am learning java!"); // I AM LEARNING JAVA!
}
}
(Later we will see how to write this more concisely using lambda expressions.)
Task 2. Number filter
Create a NumberPredicate interface with the method boolean test(int n). Implement an even check using an anonymous class.
@FunctionalInterface
interface NumberPredicate {
boolean test(int n);
}
public class PredicateDemo {
public static void main(String[] args) {
NumberPredicate isEven = new NumberPredicate() {
@Override
public boolean test(int n) {
return n % 2 == 0;
}
};
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // false
}
}
Task 3. Use standard interfaces
Instead of your own interface, you can use the ready-made Predicate<Integer>:
import java.util.function.Predicate;
Predicate<Integer> isPositive = new Predicate<Integer>() {
@Override
public boolean test(Integer x) {
return x > 0;
}
};
System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false
Table: functional interfaces from the standard library
| Interface | Method | Description | Usage example |
|---|---|---|---|
|
|
A task with no arguments and no result | Threads, timers |
|
|
A task with a result | Threads, ExecutorService |
|
|
Comparison of two objects | Sorting collections |
|
|
A “consumer” of a value | Iterating over a collection |
|
|
A “supplier” of a value | Lazy initialization, data generation |
|
|
A function from T to R | Data transformation |
|
|
Condition check | Filtering collections |
5. Common mistakes when working with functional interfaces
Error #1: you added a second abstract method. If an interface has more than one abstract method, it ceases to be functional. The compiler (especially with @FunctionalInterface) will immediately report an error.
Error #2: you forgot that default and static methods are not considered abstract. You can safely add them to a functional interface — this does not violate the rule of “one abstract method”.
Error #3: you implemented the method signature incorrectly. For example, the interface requires two arguments, but you wrote a method with only one. Always check the signatures.
Error #4: you don’t use @FunctionalInterface and accidentally break the interface. If you don’t annotate the interface, you might accidentally add an extra method — and then spend a long time figuring out why the code doesn’t work. It’s better to add the annotation for clarity.
GO TO FULL VERSION