Functional Interfaces

 

๐Ÿ”น Definition

functional interface in Java is an interface that has exactly one abstract method (SAM – Single Abstract Method).

  • They may have any number of default or static methods.
  • Functional interfaces are the foundation of lambda expressions and method references (introduced in Java 8).

๐Ÿ‘‰ They are annotated with @FunctionalInterface (optional but recommended). 


๐Ÿ”น Syntax
@FunctionalInterface interface MyFunctionalInterface { void myMethod(); // Single Abstract Method (SAM) // Allowed: default methods default void display() { System.out.println("Default method in interface"); } // Allowed: static methods static void show() { System.out.println("Static method in interface"); } } 


๐Ÿ”น Custom Functional Interfaces

You can create your own functional interfaces to suit business logic.

Example 1: Custom Greeting Interface

@FunctionalInterface interface Greeting { void sayHello(String name); } public class CustomFunctionalExample1 { public static void main(String[] args) { Greeting g = (name) -> System.out.println("Hello, " + name + "!"); g.sayHello("Siraj"); } }

Output:

Hello, Siraj!

Example 2: Calculator Interface

@FunctionalInterface interface Calculator { int operate(int a, int b); } public class CustomFunctionalExample2 { public static void main(String[] args) { Calculator add = (a, b) -> a + b; Calculator multiply = (a, b) -> a * b; System.out.println("Addition: " + add.operate(5, 3)); System.out.println("Multiplication: " + multiply.operate(5, 3)); } }

Output:

Addition: 8 Multiplication: 15

Example 3: String Formatter Interface

@FunctionalInterface interface StringFormatter { String format(String str); } public class CustomFunctionalExample3 { public static void main(String[] args) { StringFormatter upper = (s) -> s.toUpperCase(); StringFormatter reverse = (s) -> new StringBuilder(s).reverse().toString(); System.out.println("Uppercase: " + upper.format("java")); System.out.println("Reversed: " + reverse.format("java")); } }

Output:

Uppercase: JAVA Reversed: avaj 


๐Ÿ”น Built-in Functional Interfaces (java.util.function)

Java provides many ready-to-use functional interfaces inside the java.util.function package.

Categories of Built-in Functional Interfaces:

  1. Core Function Types

    • Predicate<T> – Tests a condition, returns boolean.

    • Function<T, R> – Converts input T to output R.

    • Consumer<T> – Accepts a value, returns nothing.

    • Supplier<T> – Produces a value, takes no input.

  2. Multi-Argument & Same-Type Variants

    • BiPredicate<T, U> – Tests two inputs, returns boolean.

    • BiFunction<T, U, R> – Function with two inputs.

    • BiConsumer<T, U> – Consumes two inputs, no return.

    • UnaryOperator<T> – Operation on one input (same type).

    • BinaryOperator<T> – Operation on two inputs (same type).

  3. Primitive Specializations

    • For performance, avoid boxing/unboxing.

    • IntPredicate, LongPredicate, DoublePredicate

    • IntFunction, IntConsumer, IntSupplier

    • IntUnaryOperator, IntBinaryOperator, etc.

  4. Advanced Function Interfaces

    • ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>

    • ToIntFunction<T>, ToDoubleFunction<T>, etc.

    • ToDoubleBiFunction<T, U>, etc.

  5. Other JDK Functional Interfaces

    • Runnable (method: run())

    • Callable<V> (method: call())

    • Comparable<T> (method: compareTo())

    • Event Listeners (ActionListener, FileFilter, etc.)



๐Ÿ”น Examples of Built-in Functional Interfaces

1. Core Interfaces

Predicate<T>

import java.util.function.Predicate;

public class PredicateExample { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; System.out.println(isEven.test(10)); // true } }

Output: true


Function<T, R>

import java.util.function.Function;

public class FunctionExample { public static void main(String[] args) { Function<String, Integer> lengthFunc = str -> str.length(); System.out.println(lengthFunc.apply("Siraj")); // 5 } }

Output: 5


Supplier<T>

import java.util.function.Supplier;

public class SupplierExample { public static void main(String[] args) { Supplier<Double> randomSupplier = () -> Math.random(); System.out.println(randomSupplier.get()); } }

Output: 0.65784...


Consumer<T>

import java.util.function.Consumer;

public class ConsumerExample { public static void main(String[] args) { Consumer<String> printer = msg -> System.out.println("Message: " + msg); printer.accept("Hello Functional Interfaces!"); } }

Output: Message: Hello Functional Interfaces!


2. Bi- & Same-Type Interfaces

BiPredicate<T, U>

import java.util.function.BiPredicate;

public class BiPredicateExample { public static void main(String[] args) { BiPredicate<String, Integer> longerThan = (str, len) -> str.length() > len; System.out.println(longerThan.test("Siraj", 3)); // true } }

Output: true


BiFunction<T, U, R>

import java.util.function.BiFunction;

public class BiFunctionExample { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; System.out.println(add.apply(5, 7)); // 12 } }

Output: 12


BiConsumer<T, U>

import java.util.function.BiConsumer;

public class BiConsumerExample { public static void main(String[] args) { BiConsumer<String, Integer> repeat = (word, times) -> { for (int i = 0; i < times; i++) System.out.print(word + " "); }; repeat.accept("Hi", 3); } }

Output: Hi Hi Hi


UnaryOperator<T>

import java.util.function.UnaryOperator;

public class UnaryOperatorExample { public static void main(String[] args) { UnaryOperator<Integer> square = x -> x * x; System.out.println(square.apply(6)); // 36 } }

Output: 36


BinaryOperator<T>

import java.util.function.BinaryOperator;

public class BinaryOperatorExample { public static void main(String[] args) { BinaryOperator<Integer> multiply = (a, b) -> a * b; System.out.println(multiply.apply(4, 5)); // 20 } }

Output: 20


3. Primitive Specializations

IntPredicate

import java.util.function.IntPredicate;

public class IntPredicateExample { public static void main(String[] args) { IntPredicate isPositive = n -> n > 0; System.out.println(isPositive.test(-5)); // false } }

Output: false


IntUnaryOperator

import java.util.function.IntUnaryOperator;

public class IntUnaryOperatorExample { public static void main(String[] args) { IntUnaryOperator cube = x -> x * x * x; System.out.println(cube.applyAsInt(3)); // 27 } }

Output: 27


DoubleSupplier

import java.util.function.DoubleSupplier;

public class DoubleSupplierExample { public static void main(String[] args) { DoubleSupplier piSupplier = () -> Math.PI; System.out.println(piSupplier.getAsDouble()); } }

Output: 3.141592653589793


ObjIntConsumer<T>

import java.util.function.ObjIntConsumer;

public class ObjIntConsumerExample { public static void main(String[] args) { ObjIntConsumer<String> charPrinter = (str, index) -> System.out.println(str.charAt(index)); charPrinter.accept("Siraj", 2); } }

Output: r


4. Converters

ToIntFunction<T>

import java.util.function.ToIntFunction;

public class ToIntFunctionExample { public static void main(String[] args) { ToIntFunction<String> stringLength = s -> s.length(); System.out.println(stringLength.applyAsInt("India")); // 5 } }

Output: 5


ToDoubleBiFunction<T, U>

import java.util.function.ToDoubleBiFunction;

public class ToDoubleBiFunctionExample { public static void main(String[] args) { ToDoubleBiFunction<Integer, Integer> avg = (a, b) -> (a + b) / 2.0; System.out.println(avg.applyAsDouble(10, 20)); // 15.0 } }

Output: 15.0


5. Other JDK Functional Interfaces

Runnable

public class RunnableExample { public static void main(String[] args) { Runnable task = () -> System.out.println("Running in thread!"); new Thread(task).start(); } }

Output: Running in thread!


Callable<V>

import java.util.concurrent.Callable;

public class CallableExample { public static void main(String[] args) throws Exception { Callable<String> call = () -> "Result from Callable"; System.out.println(call.call()); } }

Output: Result from Callable


Comparable<T>

public class ComparableExample implements Comparable<ComparableExample> { int age; ComparableExample(int age) { this.age = age; } @Override public int compareTo(ComparableExample other) { return this.age - other.age; } public static void main(String[] args) { ComparableExample p1 = new ComparableExample(25); ComparableExample p2 = new ComparableExample(30); System.out.println(p1.compareTo(p2)); // -5 } }
Output: -5 


๐Ÿ”น Functional Interfaces Cheat Sheet (Java 8+)

Category

Interface

Method (SAM)

Description

Example

Core

Predicate<T>

boolean test(T t)

Returns true/false

Predicate<Integer> isEven = n -> n % 2 == 0; isEven.test(10); // true


Function<T,R>

R apply(T t)

Converts input to output

Function<String,Integer> len = s -> s.length(); len.apply("Siraj"); // 5


Supplier<T>

T get()

Supplies a value

Supplier<Double> rnd = () -> Math.random(); rnd.get();


Consumer<T>

void accept(T t)

Consumes value, no return

Consumer<String> print = m -> System.out.println(m); print.accept("Hi");

Bi- & Same-Type

BiPredicate<T,U>

boolean test(T t,U u)

Tests 2 inputs

BiPredicate<String,Integer> longer = (s,l) -> s.length()>l;


BiFunction<T,U,R>

R apply(T t,U u)

Function with 2 inputs

BiFunction<Integer,Integer,Integer> add = (a,b)->a+b;


BiConsumer<T,U>

void accept(T t,U u)

Consumes 2 inputs

BiConsumer<String,Integer> repeat = (w,t)->{for(int i=0;i<t;i++) System.out.print(w+" ");};


UnaryOperator<T>

T apply(T t)

Operation on single value, same type

UnaryOperator<Integer> square = x->x*x;


BinaryOperator<T>

T apply(T t1,T t2)

Operation on 2 same type

BinaryOperator<Integer> multiply = (a,b)->a*b;

Primitive Specializations

IntPredicate

boolean test(int)

Predicate for int

IntPredicate pos = n->n>0; pos.test(-5); // false


IntUnaryOperator

int applyAsInt(int)

Unary op on int

IntUnaryOperator cube = x->x*x*x; cube.applyAsInt(3); // 27


DoubleSupplier

double getAsDouble()

Supplies double

DoubleSupplier pi = ()->Math.PI; pi.getAsDouble();


ObjIntConsumer<T>

void accept(T t,int v)

Consumes object + int

ObjIntConsumer<String> printer = (s,i)->System.out.println(s.charAt(i)); printer.accept("Siraj",2); // r

Converters

ToIntFunction<T>

int applyAsInt(T t)

Converts to int

ToIntFunction<String> len = s->s.length(); len.applyAsInt("India"); // 5


ToDoubleBiFunction<T,U>

double applyAsDouble(T t,U u)

2 inputs → double

ToDoubleBiFunction<Integer,Integer> avg = (a,b)->(a+b)/2.0;

Other JDK Functional Interfaces

Runnable

void run()

Task with no args, no result

Runnable task = ()->System.out.println("Running"); new Thread(task).start();


Callable<V>

V call()

Task with result

Callable<String> call = ()->"Result"; call.call();


Comparable<T>

int compareTo(T o)

Defines natural ordering

class P implements Comparable<P>{int age;P(int a){age=a;} public int compareTo(P o){return age-o.age;}}


In Short:

  • Functional Interface = 1 abstract method

  • Foundation of Lambda Expressions & Streams

  • Many built-in interfaces in java.util.function

  • You can also create custom functional interfaces for business logic




๐Ÿ“Š Visual Diagrams for Functional Interfaces


๐Ÿ”น 1. Functional Interface Basics

Diagram: How a Lambda Expression Implements a Functional Interface

+-----------------------------+ | Functional Interface | |-----------------------------| | abstract void myMethod(); | <--- SAM (Single Abstract Method) +-----------------------------+ ▲ | Lambda: (x) -> System.out.println(x) | +-----------------------------+ | Implementation at Runtime | +-----------------------------+

๐Ÿ‘‰ A lambda (x) -> ... is nothing but a shortcut implementation of the functional interface method.


๐Ÿ”น 2. Flow of Functional Interfaces in Streams

Example Code:

List<String> names = Arrays.asList("Amit", "Neha", "Raj", "Siraj", "Priya"); names.stream() .filter(name -> name.startsWith("S"))     // Predicate<String> .map(String::toUpperCase)        // Function<String, String> .forEach(System.out::println);         // Consumer<String>

Diagram:

Stream Pipeline ----------------------------------------------------------- [ "Amit", "Neha", "Raj", "Siraj", "Priya" ] | filter(name -> name.startsWith("S")) V [ "Siraj" ] | map(String::toUpperCase) V [ "SIRAJ" ] | forEach(System.out::println) V Prints: "SIRAJ" -----------------------------------------------------------

๐Ÿ‘‰ Each step uses a different built-in functional interface:

  • filterPredicate<T>

  • mapFunction<T, R>

  • forEachConsumer<T>


๐Ÿ”น 3. Built-in Functional Interfaces Overview

Diagram: Categories of java.util.function Interfaces

java.util.function -------------------------------------------------- | Core Types | | Predicate<T>, Function<T,R>, Consumer<T>, | | Supplier<T> | -------------------------------------------------- | Multi-argument & Same-type | | BiPredicate<T,U>, BiFunction<T,U,R>, | | BiConsumer<T,U>, UnaryOperator<T>, | | BinaryOperator<T> | -------------------------------------------------- | Primitive Specializations | | IntPredicate, IntFunction, IntSupplier, | | IntUnaryOperator, IntBinaryOperator, | | DoublePredicate, LongPredicate, etc. | -------------------------------------------------- | Advanced & Converters | | ObjIntConsumer<T>, ToIntFunction<T>, | | ToDoubleBiFunction<T,U>, etc. | -------------------------------------------------- | Other JDK Functional Interfaces | | Runnable, Callable<V>, Comparable<T>, | | ActionListener, FileFilter, etc. | --------------------------------------------------


๐Ÿ”น 4. Real-World Use Case: Button Listener

Example Code:

import javax.swing.*; import java.awt.event.*; public class ButtonExample { public static void main(String[] args) { JButton button = new JButton("Click Me"); // ActionListener is a functional interface button.addActionListener(e -> System.out.println("Button clicked!")); } }

Diagram:

[Button UI] ---> addActionListener(ActionListener) ActionListener is a Functional Interface ------------------------------------------ | void actionPerformed(ActionEvent e); | ------------------------------------------ Lambda: (e) -> System.out.println("Clicked!")


๐Ÿ”น 5. How Lambdas Replace Anonymous Classes

Before (Java 7 Style):

Runnable task = new Runnable() { public void run() { System.out.println("Running in thread!"); } }; new Thread(task).start();

After (Java 8 Lambda):

Runnable task = () -> System.out.println("Running in thread!"); new Thread(task).start();

Diagram:

Runnable (SAM: run) -------------------------------- | void run(); | -------------------------------- ▲ | Anonymous class (Java 7) | Lambda (Java 8+)


These diagrams make it easier to visualize:

  • How functional interfaces map to lambdas

  • How they are used in Streams

  • How Java internally treats them