Functional Interfaces in Java 8 – A Complete Guide

A functional interface is an interface that contains exactly one abstract method but can have multiple default and static methods. Functional interfaces enable the use of Lambda Expressions and Method References, allowing functional programming in Java.

Features of Functional Interfaces

  • Must have only one abstract method (known as SAM – Single Abstract Method).
  • Can have multiple default and static methods.
  • Marked with @FunctionalInterface annotation (optional but recommended).
  • Used with Lambda Expressions and Method References.
  • Helps in writing clean, readable, and concise code.

Creating a Functional Interface

@FunctionalInterface
interface MyFunctionalInterface {
    void displayMessage(); // Single Abstract Method (SAM)
}

Since it has only one abstract method, it qualifies as a functional interface.

Example: Using a Functional Interface with Lambda Expressions

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        // Using Lambda Expression
        MyFunctionalInterface message = () -> System.out.println("Hello, Functional Interface!");
        message.displayMessage(); // Calling the method
    }
}

Output:

Hello, Functional Interface!

Lambda expressions allow concise code instead of creating anonymous classes.

Built-in Functional Interfaces in Java 8

Java 8 provides several predefined functional interfaces in java.util.function package.

Functional InterfaceAbstract MethodDescription
Consumer<T>accept(T t)Takes one input, returns nothing.
Supplier<T>get()Takes nothing, returns one output.
Function<T, R>apply(T t)Takes one input, returns one output.
Predicate<T>test(T t)Takes one input, returns a boolean.
BiConsumer<T, U>accept(T, U)Takes two inputs, returns nothing.
BiFunction<T, U, R>apply(T, U)Takes two inputs, returns one output.
UnaryOperator<T>apply(T t)Specialized Function<T, T> (same input & output types).
BinaryOperator<T>apply(T, T)Specialized BiFunction<T, T, T> (same input & output types).

Detailed Explanation of Important Functional Interfaces

1. Consumer<T> (Takes input, returns nothing)

import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer consumer = message -> System.out.println("Message: " + message);
        consumer.accept("Hello, Java 8!"); // Calling accept() method
    }
}

Output:

Message: Hello, Java 8!

Use Case: Logging, processing each element in a collection.

2. Supplier<T> (Takes nothing, returns output)

import java.util.function.Supplier;

public class SupplierExample {
    public static void main(String[] args) {
        Supplier randomSupplier = () -> Math.random(); // Generates random number
        System.out.println("Random Number: " + randomSupplier.get());
    }
}

Use Case: Lazy initialization, fetching data from an API.

3. Function<T, R> (Takes input, returns output)

import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        Function<String, Integer> stringLength = str -> str.length();
        System.out.println("Length of 'Functional': " + stringLength.apply("Functional"));
    }
}

Use Case: Data transformation (e.g., String → Integer).

4. Predicate<T> (Returns boolean based on condition)

import java.util.function.Predicate;

public class PredicateExample {
    public static void main(String[] args) {
        Predicate isEven = number -> number % 2 == 0;
        System.out.println("Is 10 even? " + isEven.test(10));
    }
}

Use Case: Filtering elements in collections.

Functional Interface with Default & Static Methods

Even though a functional interface can have only one abstract method, it can have multiple default and static methods.

@FunctionalInterface
interface MyInterface {
    void show(); // Single abstract method

    default void defaultMethod() {
        System.out.println("This is a default method.");
    }

    static void staticMethod() {
        System.out.println("This is a static method.");
    }
}

public class DefaultStaticExample {
    public static void main(String[] args) {
        MyInterface obj = () -> System.out.println("Abstract method implemented!");

        obj.show();
        obj.defaultMethod(); // Calling default method
        MyInterface.staticMethod(); // Calling static method
    }
}

Output:

Abstract method implemented!
This is a default method.
This is a static method.

Benefit: Default & static methods allow backward compatibility without breaking existing code.

Functional Interface with Method Reference

Instead of using lambda expressions, we can use method references.

@FunctionalInterface
interface Printer {
    void print(String message);
}

public class MethodReferenceExample {
    public static void printMessage(String message) {
        System.out.println("Message: " + message);
    }

    public static void main(String[] args) {
        Printer printer = MethodReferenceExample::printMessage;
        printer.print("Using Method Reference!");
    }
}

Output:

Message: Using Method Reference!

Benefit: Improves code readability.

Custom Functional Interface Example

@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

public class FunctionalExample {
    public static void main(String[] args) {
        MathOperation addition = (a, b) -> a + b;
        MathOperation multiplication = (a, b) -> a * b;

        System.out.println("Sum: " + addition.operate(5, 10));
        System.out.println("Product: " + multiplication.operate(5, 10));
    }
}

Output:

Sum: 15
Product: 50

Use Case: Custom mathematical operations.

Summary

  • Functional Interface → Interface with one abstract method.
  • Lambda Expressions & Method References → Make functional programming possible.
  • Built-in Functional InterfacesConsumer, Supplier, Function, Predicate, etc.
  • Default & Static Methods → Allow backward compatibility.
  • Use Cases → Logging, API calls, Filtering, Transforming data, Custom operations.