Java 8 Functional Interface Examples

In Java 8, a functional interface is an interface that has exactly one abstract method. It is also known as a Single Abstract Method (SAM) interface.

Java 8 introduced functional interfaces to enable the use of lambda expressions, which provide a more concise and expressive way to write functional-style code.

To mark an interface as a functional interface, we can use the @FunctionalInterface annotation. While using the annotation is optional, it’s a good practice to add it as it helps to avoid the accidental addition of more abstract methods to the interface, thus preserving its functional nature.

Note – Without a functional interface we can’t use a lambda expression.

Important points related to the functional interface.

  1. Single Abstract Method (SAM) : A functional interface must have exactly one abstract method. This means the interface can have multiple default or static methods, but it must have only one unimplemented method that represents the primary functionality of the interface.
  2. Default Method: A functional interface can have multiple default methods, which are methods with implementations provided in the interface itself.
  3. Static Method: A functional interface can also contain multiple static methods, which are methods declared as static within the interface.

Let’s see an example of a functional interface.

@FunctionalInterface
interface MyFunctionalInterface {
    void doSomething(); // Single abstract method

    default void doSomethingElse() {
        System.out.println("Doing something else...");
    }

    default void doAnotherThing() {
        System.out.println("Doing another thing...");
    }

    static void staticMethod() {
        System.out.println("Static method called.");
    }

    static void anotherStaticMethod() {
        System.out.println("Another static method called.");
    }
}

Let’s see examples without using Java 8 Functional interface and with Java 8 functional interface

Example 1 – Use Case: Implementing a custom functional interface to define behavior.

Without using Java 8

// Without Java 8
public class WithoutJava8Example1 {
    public static void main(String[] args) {
        MyCustomFunctionalInterface myFunc = new MyCustomFunctionalInterface() {
            @Override
            public void doSomething() {
                System.out.println("Doing something!");
            }
        };
        myFunc.doSomething();
    }
}

interface MyCustomFunctionalInterface {
    void doSomething();
}

With Java 8 Functional interface


@FunctionalInterface
interface MyCustomFunctionalInterface {
    void doSomething();
}

// With Java 8
public class WithJava8Example1 {
    public static void main(String[] args) {
        MyCustomFunctionalInterface myFunc = () -> System.out.println("Doing something!");
        myFunc.doSomething();
    }
}

Example 2 – Use Case: Using a functional interface for callback mechanism.

Without using Java 8


interface CustomEventListener {
    void onEvent(String message);
}

// Without Java 8
public class WithoutJava8Example2 {
    public static void main(String[] args) {
        EventSource eventSource = new EventSource();
        eventSource.addListener(new CustomEventListener() {
            @Override
            public void onEvent(String message) {
                System.out.println("Received: " + message);
            }
        });
    }
}

class EventSource {
    public void addListener(CustomEventListener listener) {
        // Simulate an event occurring
        listener.onEvent("Event occurred!");
    }
}

With Java 8 Functional interface

// With Java 8
public class WithJava8Example2 {
    public static void main(String[] args) {
        EventSource eventSource = new EventSource();
        eventSource.addListener(message -> System.out.println("Received: " + message));
    }
}

interface CustomEventListener {
    void onEvent(String message);
}

Example 3 – Use Case: Functional interface as an argument in a method.

Without using Java 8


interface MyCustomFunctionalInterface {
    void doSomething();
}

// Without Java 8
public class WithoutJava8Example3 {
    public static void main(String[] args) {
        Processor processor = new Processor();
        processor.process(new MyCustomFunctionalInterface() {
            @Override
            public void doSomething() {
                System.out.println("Doing something in Processor!");
            }
        });
    }
}

class Processor {
    public void process(MyCustomFunctionalInterface funcInterface) {
        funcInterface.doSomething();
    }
}


With Java 8 Functional interface

interface MyCustomFunctionalInterface {
    void doSomething();
}

// With Java 8
public class WithJava8Example3 {
    public static void main(String[] args) {
        Processor processor = new Processor();
        processor.process(() -> System.out.println("Doing something in Processor!"));
    }
}


class Processor {
    public void process(MyCustomFunctionalInterface funcInterface) {
        funcInterface.doSomething();
    }
}

Example 4 – Use Case: Functional interface for strategy pattern.

Without Java 8

// Without Java 8
public class WithoutJava8Example4 {
    public static void main(String[] args) {
        Context context = new Context(new AddOperation());
        System.out.println(context.executeOperation(5, 3)); // Output: 8

        context = new Context(new SubtractOperation());
        System.out.println(context.executeOperation(5, 3)); // Output: 2
    }
}

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

class AddOperation implements MathOperation {
    @Override
    public int operate(int a, int b) {
        return a + b;
    }
}

class SubtractOperation implements MathOperation {
    @Override
    public int operate(int a, int b) {
        return a - b;
    }
}

class Context {
    private MathOperation operation;

    public Context(MathOperation operation) {
        this.operation = operation;
    }

    public int executeOperation(int a, int b) {
        return operation.operate(a, b);
    }
}

With Java 8

// With Java 8
public class WithJava8Example4 {
    public static void main(String[] args) {
        Context context = new Context((a, b) -> a + b);
        System.out.println(context.executeOperation(5, 3)); // Output: 8

        context = new Context((a, b) -> a - b);
        System.out.println(context.executeOperation(5, 3)); // Output: 2
    }
}

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

class Context {
    private MathOperation operation;

    public Context(MathOperation operation) {
        this.operation = operation;
    }

    public int executeOperation(int a, int b) {
        return operation.operate(a, b);
    }
}

Example of Java 8 Functional interface with lambda expression, Method reference, Stream API.

Functional interfaces in Java serve as a foundation for working with lambda expressions and method references, which are powerful features introduced in Java 8 for functional programming. The primary use of functional interfaces is to enable a more concise and expressive way to write code that represents behavior or actions, allowing you to treat functionality as data and pass it around easily. Here are some common use cases of functional interfaces.

  • Lambda Expressions: Functional interfaces allow you to define behavior inline using lambda expressions. Instead of creating separate classes or anonymous inner classes to implement an interface, we can directly provide a lambda expression to represent the behavior of the single abstract method of the functional interface.

Use Case 1: Sorting a List of Strings using lambda expressions and Comparator.

import java.util.Arrays;
import java.util.List;

public class LambdaExpressionsExample1 {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Sorting with lambda expression
        names.sort((s1, s2) -> s1.compareTo(s2));

        System.out.println(names);
    }
}

Output: [Alice, Bob, Charlie, David]

Use Case 2: Calculating the area of a rectangle using lambda expressions and a functional interface.

@FunctionalInterface
interface RectangleAreaCalculator {
    double calculateArea(double length, double width);
}

public class LambdaExpressionsExample2 {
    public static void main(String[] args) {
        RectangleAreaCalculator calculator = (length, width) -> length * width;
        double area = calculator.calculateArea(5.0, 3.0);

        System.out.println("Area of the rectangle: " + area);
    }
}

Output: Area of the rectangle: 15.0

  • Method References: Functional interfaces can be used in conjunction with method references to refer to an existing method that matches the functional interface’s method signature. This provides a more elegant way to express the behavior of the functional interface.

Use Case 1: Mapping a list of strings to their lengths using method references.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MethodReferencesExample1 {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Using method reference
        List<Integer> lengths = names.stream()
                                     .map(String::length)
                                     .collect(Collectors.toList());

        System.out.println(lengths);
    }
}

Output: [5, 3, 7, 5]

Use Case 2: Converting a list of integers to their corresponding strings using method

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

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

        // Using method reference
        List<String> numberStrings = numbers.stream()
                                            .map(Object::toString)
                                            .collect(Collectors.toList());

        System.out.println(numberStrings);
    }
}

Output: ["1", "2", "3", "4", "5"]

  • Stream API and Functional Programming: Functional interfaces play a crucial role in the Stream API introduced in Java 8. Operations such as map, filter, reduce, and others in the Stream API take functional interfaces as arguments to define the transformation or condition for elements in a stream.

Use Case 1: Filtering even numbers from a list using the Stream API and Predicate.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamAPIExample1 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Using Stream API and Predicate
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());

        System.out.println(evenNumbers);
    }
}

Output: [2, 4, 6, 8, 10]

Use Case 2: Summing up a list of integers using the Stream API and reduce.

import java.util.Arrays;
import java.util.List;

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

        // Using Stream API and reduce
        int sum = numbers.stream()
                         .reduce(0, Integer::sum);

        System.out.println("Sum: " + sum);
    }
}

Output: Sum: 15

  • Asynchronous Programming: Functional interfaces are used extensively in Java’s CompletableFuture and Future interfaces to define tasks that can be executed asynchronously.

Use Case 1: Using CompletableFuture for asynchronous execution of tasks.

import java.util.concurrent.CompletableFuture;

public class AsynchronousProgrammingExample1 {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            // Simulate a time-consuming task
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 42;
        });

        // Get the result asynchronously
        future.thenAccept(result -> System.out.println("Result: " + result));
    }
}

Output: Result: 42 (The exact output timing may vary due to the asynchronous nature)

Use Case 2: Using CompletableFuture to compose asynchronous tasks

import java.util.concurrent.CompletableFuture;

public class AsynchronousProgrammingExample2 {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 20)
                .thenApplyAsync(num -> num * 2)
                .thenApplyAsync(num -> num + 10);

        // Get the result asynchronously
        future.thenAccept(result -> System.out.println("Result: " + result));
    }
}

Output: Result: 50 (The exact output timing may vary due to the asynchronous nature)

  • Event Handling and Callbacks: Functional interfaces are handy when dealing with event handling and callbacks. Instead of creating custom listener interfaces with multiple methods, you can use a functional interface to define the action to be taken when an event occurs.

Use Case 1: Implementing a custom event listener with a functional interface.

@FunctionalInterface
interface CustomEventListener {
    void onEvent(String message);
}

class EventSource {
    public void addListener(CustomEventListener listener) {
        // Simulate an event occurring
        listener.onEvent("Event occurred!");
    }
}

public class EventHandlingAndCallbacksExample1 {
    public static void main(String[] args) {
        // Using the custom event listener
        EventSource eventSource = new EventSource();
        eventSource.addListener(message -> System.out.println("Received: " + message));
    }
}

Output: Received: Event occurred!

Use Case 2: Using Consumer functional interface for callback functions.

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class EventHandlingAndCallbacksExample2 {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // Using Consumer for a callback function
        Consumer<String> greetingCallback = name -> System.out.println("Hello, " + name);

        names.forEach(greetingCallback);
    }
}

Output is

Hello, Alice
Hello, Bob
Hello, Charlie
  • Functional Programming Constructs: Functional interfaces are used to define higher-order functions like Function, Predicate, Supplier, Consumer, etc., which are essential functional programming constructs in Java.

Use Case 1: Using Function to transform a list of strings to uppercase.

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class FunctionalProgrammingConstructsExample1 {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // Using Function to transform the list to uppercase
        Function<String, String> toUpperCaseFunction = String::toUpperCase;

        List<String> uppercaseNames = names.stream()
                                           .map(toUpperCaseFunction)
                                           .collect(Collectors.toList());

        System.out.println(uppercaseNames);
    }
}

Output: ["ALICE", "BOB", "CHARLIE"]

Use Case 2: Using Predicate to filter even numbers from a list of integers.

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class FunctionalProgrammingConstructsExample2 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Using Predicate to filter even numbers
        Predicate<Integer> isEven = n -> n % 2 == 0;

        List<Integer> evenNumbers = numbers.stream()
                                           .filter(isEven)
                                           .collect(Collectors.toList());

        System.out.println(evenNumbers);
    }
}

Output: [2, 4, 6, 8, 10]

  • Functional Composition: Functional interfaces enable the composition of functions using default methods like andThen, compose, etc. This allows you to chain multiple behaviors together to create more complex functionality.

Use Case 1: Composing two functions using Function‘s andThen method.

import java.util.function.Function;

public class FunctionalCompositionExample1 {
    public static void main(String[] args) {
        Function<Integer, Integer> addOne = num -> num + 1;
        Function<Integer, Integer> multiplyByTwo = num -> num * 2;

        Function<Integer, Integer> addOneThenMultiply = addOne.andThen(multiplyByTwo);

        int result = addOneThenMultiply.apply(5);

        System.out.println("Result: " + result);
    }
}

Output: Result: 12

Use Case 2: Composing two functions using Function‘s compose method.

import java.util.function.Function;

public class FunctionalCompositionExample2 {
    public static void main(String[] args) {
        Function<Integer, Integer> addOne = num -> num + 1;
        Function<Integer, Integer> multiplyByTwo = num -> num * 2;

        Function<Integer, Integer> multiplyThenAddOne = addOne.compose(multiplyByTwo);

        int result = multiplyThenAddOne.apply(5);

        System.out.println("Result: " + result);
    }
}

Output: Result: 11

Predefined Java 8 functional interfaces examples

1. Predicate<T>

import java.util.function.Predicate;

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

The Predicate functional interface takes an argument and returns a boolean value. In this example, isEven is a predicate that checks whether a given integer is even.

2. Function<T, R>

import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        Function<Integer, String> intToString = num -> "Number: " + num;
        
        String result = intToString.apply(42);
        System.out.println(result); // Output: Number: 42
    }
}

The Function functional interface takes an argument of type T and returns a result of type R. In this example, intToString converts an integer to a string with a prefix.

3. Consumer<T>

import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> printMessage = message -> System.out.println(message);
        
        printMessage.accept("Hello, world!"); // Output: Hello, world!
    }
}

The Consumer functional interface takes an argument of type T and performs some action without returning a result. In this example, printMessage prints the provided message to the console.

4. Supplier<T>

import java.util.function.Supplier;

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

The Supplier functional interface takes no arguments and returns a result of type T. In this example, randomValue supplies a random double value using the Math.random() method.

5. UnaryOperator<T>

import java.util.function.UnaryOperator;

public class UnaryOperatorExample {
    public static void main(String[] args) {
        UnaryOperator<Integer> square = num -> num * num;
        
        System.out.println(square.apply(5)); // Output: 25
    }
}

The UnaryOperator functional interface takes an argument of type T and returns a result of the same type T. In this example, square calculates the square of an integer.

6. BinaryOperator<T>

import java.util.function.BinaryOperator;

public class BinaryOperatorExample {
    public static void main(String[] args) {
        BinaryOperator<Integer> sum = (a, b) -> a + b;
        
        System.out.println(sum.apply(3, 5)); // Output: 8
    }
}

The BinaryOperator functional interface takes two arguments of type T and returns a result of the same type T. In this example, sum calculates the sum of two integers.

These examples demonstrate how to use various predefined Java 8 functional interfaces to achieve different types of operations using lambda expressions.

Questions and answers related to Java 8 functional interface.

1: What is a functional interface in Java 8? Answer: A functional interface in Java 8 has a single abstract method and serves as the basis for lambda expressions.

2: How is a lambda expression related to a functional interface? Answer: Lambda expressions offer a concise way to implement the single method of a functional interface.

3: What is the purpose of the java.util.function package in Java 8? Answer: The java.util.function package provides built-in functional interfaces that aid functional programming tasks.

4: What are method references, and how do they relate to functional interfaces? Answer: Method references are shortcuts to refer to methods, often used with functional interfaces as compact alternatives to lambda expressions.

5: What are the key differences between Function and Consumer functional interfaces? Answer: Function takes an input and returns a value, while Consumer takes an input and performs an action without returning anything.

6: How do you define a custom functional interface? Answer: You define a custom functional interface by declaring a single abstract method within it.

7: What is the purpose of the @FunctionalInterface annotation? Answer: The @FunctionalInterface annotation ensures that an interface has only one abstract method, highlighting its functional nature.

8: What does the term “SAM interface” mean in the context of functional interfaces? Answer: “SAM” stands for Single Abstract Method. A SAM interface is a functional interface with only one abstract method.

9: What is the advantage of using method references over lambda expressions? Answer: Method references can lead to more concise and readable code when you’re directly referring to an existing method.

10: How do functional interfaces enhance the Java programming paradigm? Answer: Functional interfaces promote a more modular and expressive code style, enabling the adoption of functional programming concepts in Java.

Check Java 8 functional interface docs.

Other Java 8 examples.

That’s all about Java 8 Functional Interface Examples.