Default Functional Interfaces in Java

Saami abbas Khan - Sep 18 - - Dev Community

Just Finished Studying Default Functional Interfaces in Java, I thought of sharing them all!
Functional interfaces are interfaces that have only one abstract method. They are necessary if you'll be dealing with lambda expressions (functional programming). They simplify code and are widely used in streams. While you can create your own functional interfaces, why worry when Java provides us with some important ones like Consumer, Predicate, Function, and Supplier?

1. Consumer:

Consumer is a functional interface that represents an operation that accepts a single input argument and returns no result. It’s typically used to perform an action on the given argument (like printing or logging) without modifying it.

Signature: void accept(T t) (where T is the Generic type)

2. Predicate:

Predicate is a functional interface that represents a single argument function that returns a boolean value. It’s often used for filtering or evaluating conditions (e.g., checking if a number is even).

Signature: boolean test(T t)

3. Function:

Function is a functional interface that represents a function that accepts one argument and produces a result. It’s commonly used for transformations (e.g., converting one type to another or modifying data).

Signature: R apply(T t)

4. Supplier:

Supplier is a functional interface that represents a function with no input arguments and returns a result. It’s often used for generating or supplying values without needing input.

Signature: T get()

We can effectively use functional interfaces like Consumer, Predicate, Function, and Supplier by typically defining generic methods that accept these interfaces as parameters. This allows us to leverage the power of generics and ensure that our methods can work with various types.

Here's example of the complete code that demonstrates the functionality of all of them

import java.util.List;
import java.util.Random;
import java.util.function.*;

public class Main {
    public static void main(String[] args) {
        // Consumer
        usingConsumer((a) -> System.out.printf("Hello %s", a), "saami");
        System.out.println();
        // Bi-Consumer
        usingBiConsumer((a, b) -> System.out.printf("Name: %s, Age: %d", a, b), "saami", 20);
        System.out.println();
        // Predicate
        var result1 = usingPredicate((a) -> a % 2 == 0, 34);

        if (result1) {
            System.out.println("Even");
        } else {
            System.out.println("Odd");
        }
        // Bi-Predicate
        var result2 = usingBiPredicate((a, b) -> a > b, 12, 22);
        if (result2) {
            System.out.println("Greater");
        } else {
            System.out.println("Lesser");
        }
        // Function
        var result3 = usingFunction((a) -> a + ": this is a number", 5);
        System.out.println(result3);

        // Bi-Function
        var result4 = usingBiFunction((a, b) -> (a > b ? "Greater": "Lesser"), 5, 6);
        System.out.println(result4);

        // Unary-Operator
        var result5 = usingUnaryOperator((a) -> a+5, 10);
        System.out.println(result5);

        // Binary-Operator
        var result6 = usingBinaryOperator((a, b) -> a + b, 12, 32);
        System.out.println(result6);
        Random r = new Random();


        // Function as Predicate
        var result7 = usingFunctionAsPredicate((a) -> a > 99, 999);
        System.out.println(result7);

        // Using Consumer for printing of the list.
        printData((a) -> {
            for (var ele : a) {
                System.out.println(ele);
            }
        } , List.of("S1", "S2", "S3", "S4", "S5"));

        // Using Supplier as a random number generator
        String[] arr = {"saami", "john", "raymond", "puff"};
        System.out.println(getRandomOne(arr, () -> new Random().nextInt(arr.length)));

        // Using Custom Comparator
        System.out.println(usingCustomFunctionalInterface((a, b, c) -> a + b + c, "Saami", " Abbas", " Khan"));

    }

    public static <T> void usingConsumer(Consumer<T> consumer, T a) {
        // Method that takes consumer interface will return void.
        // Can print something constituting 'a'
        consumer.accept(a);
    }

    public static <T, L> void usingBiConsumer(BiConsumer<T, L> biConsumer, T a, L b) {
        biConsumer.accept(a, b);
    }

    public static <T> boolean usingPredicate(Predicate<T> predicate, T a) {
        return predicate.test(a);
    }

    public static <T, L> boolean usingBiPredicate(BiPredicate<T, L> biPredicate, T a, L b) {
        return biPredicate.test(a, b);
    }

    public static <T, R> R usingFunction(Function<T, R> function, T a) {
//        T for the parameter and R for the return type here the return type could be as same as T or
//        could be different like if T is Integer the R could be String 8 + ""
        return function.apply(a);
    }

    public static <T, U, R> R usingBiFunction(BiFunction<T, U, R> biFunction, T a, U b) {
        return biFunction.apply(a, b);
    }

    public static <T> T usingUnaryOperator(UnaryOperator<T> unaryOperator, T a) {
        return unaryOperator.apply(a);
    }

    public static <T> T usingBinaryOperator(BinaryOperator<T> binaryOperator, T a, T b) {
        return binaryOperator.apply(a, b);
    }

    public static <T, R> R usingFunctionAsPredicate(Function<T, R> prediFunction, T a) {
        return prediFunction.apply(a);
    }

    public static <T> void printData(Consumer<T> consumer, T a) {
        /*
         * Prints the data, (List.of()) using a for loop inside of lambda function.
         */
        consumer.accept(a);
    }

    public static String getRandomOne(String[] arr, Supplier<Integer> supplier) {
        return arr[supplier.get()];
    }

    @FunctionalInterface
    interface Concat<T> {
        T concat(T a, T b, T c);
    }
    public static <T> T usingCustomFunctionalInterface(Concat<T> concat, T a, T b, T c) {
        return concat.concat(a, b, c);
    }


}


Enter fullscreen mode Exit fullscreen mode

Final Verdict

Functional interfaces in Java are a powerful tool for simplifying code and improving readability. Whether you're processing collections, performing transformations, or handling data flow, these interfaces make it easier to define concise operations.

By understanding and applying functional interfaces like Consumer, Predicate, Function, Supplier, and custom ones, you can take full advantage of Java’s functional programming features.

. .
Terabox Video Player