🔌 Interfaces & Abstraction · Intermediate

Functional interfaces in Java

Exactly one abstract method; @FunctionalInterface.

🧩 The mystery(a, b) -> a + b — no class, no method name, no implements. Yet Java knows exactly which interface it implements. How?

One hole, one lambda

A functional interface has exactly one abstract method. A lambda supplies the body for that one method, so Java always knows which method the arrow implements.

@FunctionalInterface
interface Op {
    int apply(int a, int b);
}
Op add = (a, b) -> a + b;
add.apply(2, 3);   // 5
🔮 Predict it

Two lambdas, one interface

What does this print?

interface Op {
    int apply(int a, int b);
}
void main() {
    Op max = (a, b) -> a > b ? a : b;
    Op sub = (a, b) -> a - b;
    int r = max.apply(4, 9) + sub.apply(4, 9);
    System.out.println(r);
}
  1. 4
  2. 14
  3. 9-5
Show the answer

Each lambda is a separate implementation of apply. max gives 9 and sub gives -5; both are ints, so + adds them: 4.

@FunctionalInterface: a safety net

The annotation is optional, but it makes the compiler enforce the one-method rule. Only *abstract* methods count: an interface with one abstract method plus three default or static methods is still functional.

@FunctionalInterface
interface Task {
    void run();
    default void log() { }   // fine
}
⚠️ The trap

Two abstract methods? Not functional

Add a second abstract method and @FunctionalInterface turns into a compile error. Interfaces like List<E> (add, get, size…) can never be lambda targets. Runnable, Predicate<T>, Supplier<T>, Function<T,R> and Comparator<T> can.

@FunctionalInterface
interface Task {
    void run();
    void stop();   // compile error
}
🤔 Think first

The Comparator puzzle

Comparator<T> declares int compare(T a, T b) AND boolean equals(Object o), both abstract. Why is it still a functional interface?

Think about it, then reveal the answer

equals matches a **public method of Object, and every class already inherits that. So abstract methods that redeclare public Object methods don't count**. Only compare is left.

💼 In the real world

Lambdas everywhere

The java.util.function package (Supplier, Function, Predicate, Consumer…) is what makes streams work: list.stream().filter(s -> s.isEmpty()). You also pass lambdas as Runnable to threads and as Comparator to sort. Each is just one abstract method.

Key takeaways

  1. Exactly one abstract method (defaults and statics don't count)
  2. @FunctionalInterface makes the compiler check it
  3. Built-ins: Runnable, Supplier<T>, Function<T,R>, Predicate<T>, Comparator<T>
  4. Abstract methods matching public Object methods (like equals) don't count
🤯 Did you know?

Runnable (1996) and Comparator (1998) became lambda targets in 2014 with Java 8 — without changing their methods. They had exactly one abstract method all along.

Practice questions

What does this print?

@FunctionalInterface
interface Op {
    int apply(int a, int b);
}
void main() {
    Op add = (a, b) -> a + b;
    Op mul = (a, b) -> a * b;
    System.out.println(add.apply(2, 3) + mul.apply(2, 3));
}
  1. 11
  2. 5 6
  3. 56
  4. Compile error
Check your answer

11. Each lambda implements apply. add gives 5 and mul gives 6; both are ints, so + adds them to 11.

What does this print?

@FunctionalInterface
interface Task {
    void run();
    void stop();
}
void main() {
    System.out.println("ok");
}
  1. ok
  2. Compile error
  3. Throws IllegalStateException
Check your answer

Compile error. The annotation asks the compiler to verify there's exactly one abstract method. Task has two, so compilation fails.

Next: what good is an interface with NO methods at all? Java's Serializable is exactly that.