Skip to main content

8.2 Functional Interfaces

← Back to Chapter 8: Java 8 Features

A Functional Interface is an interface that contains exactly one abstract method (SAM - Single Abstract Method). Functional interfaces serve as the target types for lambda expressions and method references.


What is a Functional Interface?​

A Functional Interface is an interface that has exactly one abstract method.

For example:

@FunctionalInterface
interface Calculator {

int add(int a, int b); // The one abstract method
}

Here Calculator is a Functional Interface because it has only one abstract method: add().

The purpose is mainly to allow us to use a Lambda expression with that interface. Java's official documentation defines a functional interface as one with exactly one abstract method, while methods with implementations such as default methods do not count.

Why do we need Functional Interface?​

Suppose we have:

interface Calculator {
int add(int a, int b);
}

We can implement it using Lambda:

Calculator calculator = (a, b) -> a + b;

Think of it like this:

Functional Interface
↓
Calculator
↓
add()
↓
Lambda
↓
(a, b) -> a + b

Java knows that:

(a, b) -> a + b

is the implementation of:

add(a, b)

because Calculator has only one abstract method.

Why exactly ONE abstract method?​

To avoid any confusion for compiler that whcich method the Lambda Expression has to execute.

But can Functional Interface have other methods?​

Yes! This is an important interview question.

A Functional Interface can have:

1 abstract method
+ any number of default methods
+ any number of static methods

For example:

@FunctionalInterface
interface Calculator {

int add(int a, int b); // Only abstract method

default void print() {
System.out.println("Calculator");
}

static void info() {
System.out.println("This is Calculator");
}
}

This is still a Functional Interface.

Why?

Because print() already has an implementation and info() is static.

So only add() is an abstract method.

Java's specification explicitly treats default methods as non-abstract for this purpose.

What is @FunctionalInterface?​

You will commonly see:

@FunctionalInterface
interface Calculator {
int add(int a, int b);
}

@FunctionalInterface is an annotation.

It tells the compiler:

I intend this interface to be a Functional Interface.

If somebody later accidentally adds another abstract method:

@FunctionalInterface
interface Calculator {

int add(int a, int b);

int subtract(int a, int b); // Error
}

The compiler will complain.

So @FunctionalInterface gives us compile-time protection.

Is @FunctionalInterface mandatory?​

No.

This is a Functional Interface:

interface Calculator {
int add(int a, int b);
}

Even without:

@FunctionalInterface

Java can recognize it as a Functional Interface because it has one abstract method.

The annotation simply tells the compiler and other developers that the interface is intended to be functional.

Common Functional Interfaces provided by Java​

Java 8 already provided many ready-made Functional Interfaces in:

java.util.function

Functional InterfaceMeaning
Predicate<T>Takes input → returns boolean
Function<T,R>Takes input → returns result
Consumer<T>Takes input → returns nothing
Supplier<T>Takes nothing → supplies a value
UnaryOperator<T>Takes T → returns T
BinaryOperator<T>Takes two T → returns T

1. Predicate<T>​

Evaluates a boolean condition against an input:

Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> startsWithA = s -> s.startsWith("A");

// Composing predicates
Predicate<String> validUser = isLong.and(startsWithA);
System.out.println(validUser.test("Alexander")); // true
System.out.println(validUser.test("Alex")); // false

2. Function<T, R>​

Accepts an argument of type T and transforms it to type R:

Function<String, Integer> stringLength = s -> s.length();
Function<Integer, Integer> doubleValue = n -> n * 2;

// Function composition with andThen() and compose()
Function<String, Integer> pipeline = stringLength.andThen(doubleValue);
System.out.println(pipeline.apply("Java")); // (4) * 2 = 8

3. Consumer<T>​

Accepts an argument and executes side-effects without returning a value:

Consumer<String> printer = s -> System.out.println("Item: " + s);
Consumer<String> logger = s -> System.out.println("Logged: " + s);

// Chaining consumers with andThen()
Consumer<String> combined = printer.andThen(logger);
combined.accept("Order #101");

4. Supplier<T>​

Takes no parameters and supplies an instance of type T:

Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get());

Supplier<List<String>> listFactory = ArrayList::new;
List<String> list = listFactory.get();