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 Interface | Method Signature | Purpose |
---|---|---|
Runnable | void run() | Represents a task to be executed in a thread (no input, no output). |
Callable | V call() | Similar to Runnable but can return a result and throw exceptions. |
Comparable | int compareTo(T o) | Compares two objects for ordering. |
Predicate | boolean test(T t) | Takes an input and returns a boolean (used in conditions/filters). |
Consumer | void accept(T t) | Consumes an input, performs an action, but does not return. |
Supplier | T get() | Supplies a value without taking input. |
Function | R 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 Interface | Description | Method | Example |
---|---|---|---|
Runnable | Represents a task executed by a thread | void run() | new Thread(() -> {...}).start(); |
Callable | Task that returns result | V call() | Callable c = () -> 5; |
Comparable | Compares two objects | int compareTo(T o) | s1.compareTo(s2) |
Consumer | Accepts input, returns nothing | void accept(T t) | Consumer c = x -> ...; |
Predicate | Accepts input, returns boolean | boolean test(T t) | Predicate p = s -> ...; |
Function | Input → Result | R apply(T t) | Function f = x -> x*x; |
Supplier | No input, returns result | T get() | Supplier s = () -> "Hi"; |
BiConsumer | Two inputs, no result | void accept(T, U) | (a,b)->System.out.println(a+b) |
BiPredicate | Two inputs, boolean result | boolean test(T,U) | (a,b)->a>b |
BiFunction | Two inputs, one result | R apply(T,U) | (a,b)->a+b |
UnaryOperator | Input & output same | T apply(T t) | x -> x*x |
BinaryOperator | Two inputs, output same type | T apply(T,T) | (a,b)->a+b |
10. Key Takeaways
- A functional interface has exactly one abstract method.
- Lambda expressions and method references rely on functional interfaces.
- Java provides built-in functional interfaces in java.util.function.
- Major types: Consumer, Predicate, Function, Supplier.
- Extensively used in multithreading, event handling, and the Streams API.