Advanced Java August 19 ,2025

Java Functional Interfaces

1. Introduction

A functional interface in Java is an interface that contains exactly one abstract method (also known as a Single Abstract Method or SAM interface).

  • From Java 8 onwards, functional interfaces became the foundation for Lambda Expressions and Method References, allowing Java to support functional-style programming.
  • A functional interface may include multiple default or static methods, but only one abstract method.

Examples of Functional Interfaces

  • Runnable (method run())
  • Callable (method call())
  • Comparator (method compare())
  • Predicate, Function, Consumer, Supplier (introduced in Java 8)

2. Using Functional Interfaces with Lambda Expressions

A lambda expression provides a clear and concise way to implement the abstract method of a functional interface.

Example – Runnable with Lambda

public class Geeks {
    public static void main(String[] args) {
        // Using lambda expression to implement Runnable
        new Thread(() -> System.out.println("New thread created")).start();
    }
}

Output:

New thread created

Explanation:

  • Runnable is a functional interface with one method run().
  • The lambda () -> System.out.println("New thread created") is the implementation of run().
  • When the thread starts, it executes the lambda body.

3. @FunctionalInterface Annotation

The @FunctionalInterface annotation is used to mark an interface as functional.

  • It ensures that the interface has only one abstract method.
  • If more than one abstract method is declared, the compiler throws an error.
  • Although optional, it is best practice to use this annotation.

Example

@FunctionalInterface
interface Square {
    int calculate(int x);
}

class Geeks {
    public static void main(String args[]) {
        int a = 5;

        // Lambda expression implementing calculate()
        Square s = (int x) -> x * x;

        System.out.println(s.calculate(a));
    }
}

Output:

25

4. Functional Interfaces Before Java 8

Before Java 8, functional interfaces were implemented using anonymous inner classes.

Example

class Geeks {
    public static void main(String args[]) {
        // Anonymous inner class implementation
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("New thread created");
            }
        }).start();
    }
}

Output:

New thread created

Observation:

  • This approach is verbose compared to Java 8’s lambda syntax.
  • Lambda expressions make functional interface usage much cleaner.

5. Built-in Functional Interfaces

Java 8 introduced the package java.util.function, which provides many ready-to-use functional interfaces. These interfaces are designed to work with lambda expressions and method references, making code more concise and expressive.

Apart from java.util.function, some older interfaces from java.lang (like Runnable and Comparable) also qualify as functional interfaces because they have only one abstract method (SAM – Single Abstract Method).

Common Built-in Functional Interfaces

Functional InterfaceMethod SignaturePurpose
Runnablevoid run()Represents a task to be executed in a thread (no input, no output).
CallableV call()Similar to Runnable but can return a result and throw exceptions.
Comparableint compareTo(T o)Compares two objects for ordering.
Predicateboolean test(T t)Takes an input and returns a boolean (used in conditions/filters).
Consumervoid accept(T t)Consumes an input, performs an action, but does not return.
SupplierT get()Supplies a value without taking input.
FunctionR apply(T t)Takes input and produces a result (used in transformations).

6. Types of Functional Interfaces

Java 8 broadly categorizes functional interfaces into four major types:

6.1 Consumer

  • Theory:
    A Consumer takes one input argument but does not return any result. It is generally used for performing operations on the given input such as printing, logging, or modifying objects.
  • Method:

    void accept(T t)
    
  • Example:

    import java.util.function.Consumer;
    
    class Demo {
        public static void main(String[] args) {
            Consumer consumer = (value) -> System.out.println("Value: " + value);
            consumer.accept(10); // prints Value: 10
        }
    }
    
  • Output:

    Value: 10
    
  • Variants:
    • IntConsumer, DoubleConsumer, LongConsumer – work with primitives.
    • BiConsumer – accepts two inputs and returns nothing.

6.2 Predicate

  • Theory:
    A Predicate takes one argument and returns a boolean result. It is widely used for filtering data, conditional checks, and stream operations.
  • Method:

    boolean test(T t)
    
  • Example: Filtering strings starting with "G".

    import java.util.*;
    import java.util.function.Predicate;
    
    class Demo {
        public static void main(String[] args) {
            List list = Arrays.asList("Geek", "GeeksQuiz", "g1", "QA", "Geek2");
    
            Predicate p = (s) -> s.startsWith("G");
    
            for (String str : list) {
                if (p.test(str)) {
                    System.out.println(str);
                }
            }
        }
    }
    
  • Output:

    Geek
    GeeksQuiz
    Geek2
    
  • Variants:
    • IntPredicate, DoublePredicate, LongPredicate – work with primitives.
    • BiPredicate – takes two inputs and returns a boolean.

6.3 Function

  • Theory:
    A Function takes one input and produces one result. It is useful for data transformation (e.g., converting, mapping, or processing values).
  • Method:

    R apply(T t)
    
  • Example: Squaring a number.

    import java.util.function.Function;
    
    class Demo {
        public static void main(String[] args) {
            Function function = x -> x * x;
            System.out.println(function.apply(5)); // 25
        }
    }
    
  • Output:

    25
    
  • Variants:
    • BiFunction – takes two inputs and returns one output.
    • UnaryOperator – takes one input and returns the same type.
    • BinaryOperator – takes two inputs of the same type and returns the same type.

6.4 Supplier

  • Theory:
    A Supplier provides a value without requiring any input. It is often used for lazy evaluation, object creation, or data generation.
  • Method:

    T get()
    
  • Example: Returning a string.

    import java.util.function.Supplier;
    
    class Demo {
        public static void main(String[] args) {
            Supplier supplier = () -> "Hello, World!";
            System.out.println(supplier.get());
        }
    }
    
  • Output:

    Hello, World!
    
  • Variants:
    • BooleanSupplier, IntSupplier, DoubleSupplier, LongSupplier – return primitive values.

7. Extended Functional Interfaces

  • BiConsumer → Accepts two inputs, no return.
  • BiPredicate → Accepts two inputs, returns boolean.
  • BiFunction → Accepts two inputs, returns result.
  • UnaryOperator → Special case of Function where input and output types are the same.
  • BinaryOperator → Special case of BiFunction where both inputs and output are the same type.

8. Functional Interfaces in Streams

Functional interfaces are widely used in Java Streams API for functional-style operations.

Example – Using Predicate and Consumer with Streams:

import java.util.*;
import java.util.stream.*;

class Geeks {
    public static void main(String[] args) {
        List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        numbers.stream()
               .filter(n -> n % 2 == 0)       // Predicate
               .forEach(n -> System.out.println(n)); // Consumer
    }
}

Output:

2
4
6

9. Functional Interfaces Summary Table

Functional InterfaceDescriptionMethodExample
RunnableRepresents a task executed by a threadvoid run()new Thread(() -> {...}).start();
CallableTask that returns resultV call()Callable c = () -> 5;
ComparableCompares two objectsint compareTo(T o)s1.compareTo(s2)
ConsumerAccepts input, returns nothingvoid accept(T t)Consumer c = x -> ...;
PredicateAccepts input, returns booleanboolean test(T t)Predicate p = s -> ...;
FunctionInput → ResultR apply(T t)Function f = x -> x*x;
SupplierNo input, returns resultT get()Supplier s = () -> "Hi";
BiConsumerTwo inputs, no resultvoid accept(T, U)(a,b)->System.out.println(a+b)
BiPredicateTwo inputs, boolean resultboolean test(T,U)(a,b)->a>b
BiFunctionTwo inputs, one resultR apply(T,U)(a,b)->a+b
UnaryOperatorInput & output sameT apply(T t)x -> x*x
BinaryOperatorTwo inputs, output same typeT apply(T,T)(a,b)->a+b

10. Key Takeaways

  1. A functional interface has exactly one abstract method.
  2. Lambda expressions and method references rely on functional interfaces.
  3. Java provides built-in functional interfaces in java.util.function.
  4. Major types: Consumer, Predicate, Function, Supplier.
  5. Extensively used in multithreading, event handling, and the Streams API.

 

Next Blog- Java Stream API

 

 

Sanjiv
0

You must logged in to post comments.

Get In Touch

123 Street, New York, USA

+012 345 67890

techiefreak87@gmail.com

© Design & Developed by HW Infotech