Java Functional Interfaces with Examples

In this article, we will learn Java Functional Interfaces which are coming by default in Java. These interfaces are; Supplier, Consumer, Predicate, Function, Runnable, and Callable. First of all, I highly suggest you use Java 8 and higher versions of Java to work with these interfaces. Currently, the latest LTS version is Java 17 and I will do these examples with Java 17 JDK. You can download the Java 17 JDK here and for installation please read here.

Java Functional Interfaces

Let’s start with the Supplier interface. 

Supplier Interface

The Supplier Interface does not allow input, it returns a value based on a defined type. It is like a function without parameters, with a return type. Now, let’s see this behavior with examples.

public class SupplierInterface {
    //Supplier function declarations.
    Supplier<String>  textSupplier     = () -> "Hello SW Test Academy!";
    Supplier<Integer> numberSupplier   = () -> 1234;
    Supplier<Double>  randomSupplier   = () -> Math.random();
    Supplier<Double>  randomSupplierMR = Math::random; //With Method Reference (MR)

    @Test
    public void supplierTest() {
        //Calling Supplier functions.
        System.out.println(textSupplier.get());
        System.out.println(numberSupplier.get());
        System.out.println(randomSupplier.get());
        System.out.println(randomSupplierMR.get());
    }
}

Output

java functional interfaces

Consumer Interface

The Consumer Interface takes an input, it does not return a value. It is like a function with a parameter, without a return type. BiConsumer Interface takes two inputs and does not return anything. That’s why it is called “Bi” Consumer. If we chain multiple consumers with andThen method, first the first consumer will be executed and the second one executed. It is working like left to right flow.

Now, it is an examples time for each case. :)

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ConsumerInterface {
    //Consumer function declarations.
    Consumer<String> upperCaseConsumer = (text) -> System.out.println(text.toUpperCase());
    Consumer<String> lowerCaseConsumer = (text) -> System.out.println(text.toLowerCase());
    Consumer<Double> logOfTenConsumer  = (number) -> System.out.println(Math.log10(number));

    //BiConsumer takes two parameters and does not return anything!
    BiConsumer<Integer, Integer> powConsumer = (base, power) -> System.out.println(Math.pow(base, power));

    @BeforeEach
    public void setup(TestInfo testInfo) {
        System.out.println("Test name: " + testInfo.getDisplayName());
    }

    @AfterEach
    public void tearDown(){
        System.out.println();
    }

    @Order(1)
    @Test
    public void consumerTest() {
        //Calling Consumer functions.
        upperCaseConsumer.accept("Hello SW Test Academy!");
        lowerCaseConsumer.accept("Hello SW Test Academy!");
        logOfTenConsumer.accept(1000.00);
    }

    @Order(2)
    @Test
    public void biConsumerTest() {
        //Calling BiConsumer function.
        powConsumer.accept(3,2);
    }

    @Order(3)
    @Test
    public void consumerChainTest() {
        //Consumer chaining with andThen method.
        upperCaseConsumer
            .andThen(lowerCaseConsumer)
            .accept("Hello SW Test Academy!");
    }
}

Output

java consumer interface

Function Interface

The Function Interface takes an input, it returns a defined type. It is like a function with a parameter, with a return type. The first declaration is input, the second is the return type. The BiFunction Interface takes two inputs rather than one input. That’s the only difference between Function and BiFunction interfaces. Also, UnaryOperator interface takes and returns the same type. 

If we chain the functions with andThen method, the execution order will be like the left to right flow. First, the first function will be run, then the others will be run. If we want to run these functions right to left we can use compose method rather than the andThen method. 

Now, examples time to see all of these behaviors in action!

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class FunctionInterface {
    //FunctionInterface function declarations. (Input Type, Return Type)
    Function<String, String>  toUpperCase = (text) -> text.toUpperCase();
    Function<String, String>  toLowerCase = (text) -> text.toLowerCase();
    Function<Integer, Double> log10       = (number) -> Math.log10(number);

    //Method Reference Declarations (Input Type, Return Type)
    Function<String, String>  toUpperCaseMR = String::toUpperCase;
    Function<String, String>  toLowerCaseMR = String::toLowerCase;
    Function<Integer, Double> log10MR       = Math::log10;

    //BiFunction Example (Input Type, Input Type, Return Type)
    BiFunction<Integer, Integer, Integer> powerOf = (base, power) -> (int) Math.pow(base, power);

    //UnaryOperator Example (Input and Return type are same.)
    UnaryOperator<String> appendText = (text) -> "I am appending: " + text;

    @BeforeEach
    public void setup(TestInfo testInfo) {
        System.out.println("Test name: " + testInfo.getDisplayName());
    }

    @AfterEach
    public void tearDown(){
        System.out.println();
    }

    @Order(1)
    @Test
    public void functionTest() {
        //Calling functions.
        String upperCaseResult = toUpperCase.apply("hello sw test academy!");
        Double log10Result = log10.apply(10000);

        System.out.println(upperCaseResult);
        System.out.println(log10Result);
    }

    @Order(2)
    @Test
    public void functionChainWithAndThen() {
        //Function chaining. First do the first function then do the second one.
        String chainResult1 = toUpperCase.andThen(toLowerCase).apply("heLLo sW teSt ACadEmy!");
        String chainResult2 = toLowerCase.andThen(toUpperCase).apply("heLLo sW teSt ACadEmy!");

        System.out.println(chainResult1);
        System.out.println(chainResult2);
    }

    @Order(3)
    @Test
    public void functionChainWithCompose() {
        //Function chaining. First do the second function then do the first one. Vise versa of andThen.
        String chainResult1 = toUpperCase.compose(toLowerCase).apply("heLLo sW teSt ACadEmy!");
        String chainResult2 = toLowerCase.compose(toUpperCase).apply("heLLo sW teSt ACadEmy!");

        System.out.println(chainResult1);
        System.out.println(chainResult2);
    }

    @Order(4)
    @Test
    public void biFunctionTest() {
        //Calling functions.
        int result = powerOf.apply(3, 2);
        System.out.println("Power of 3 over 2 is: " + result);
    }

    @Order(5)
    @Test
    public void unaryOperatorTest(){
        //Calling UnaryOperator
        System.out.println(appendText.apply("Hello SW Test Academy!"));
    }
}

Output

Java Function Interface

Predicate Interface

The predicate takes an input, it returns a boolean value as true or false. It is like a function with a parameter, with the boolean return type. BiPredicate Interface takes two inputs and returns a boolean value.

Negate method does the inversion for the value.

And method is working as logical AND operation.

Or method is working as a logical OR operation.

Let’s see all of these with examples.

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PredicateInterface {
    //Predicate function declaration.
    String            sampleText        = "Hello SW Test Academy";
    Predicate<String> containsPredicate = (text) -> sampleText.contains(text);

    //BiPredicate function declaration.
    BiPredicate<String, String> containsBiPredicate   = (text, pattern) -> text.contains(pattern);
    BiPredicate<String, String> containsBiPredicateMR = String::contains; //Method reference version.

    @BeforeEach
    public void setup(TestInfo testInfo) {
        System.out.println("Test name: " + testInfo.getDisplayName());
    }

    @AfterEach
    public void tearDown(){
        System.out.println();
    }

    @Order(1)
    @Test
    public void predicateTest() {
        //Calling Predicate functions.
        boolean result = containsPredicate.test("SW");
        boolean resultOfNegate = containsPredicate.negate().test("SW"); //negate is inverse operation like "does not contain".
        boolean andResult = containsBiPredicate.and(containsBiPredicate.negate()).test("SW", "SW"); //Logical AND operation.
        boolean orResult = containsBiPredicate.or(containsBiPredicate.negate()).test("SW", "SW"); //Logical OR operation.

        System.out.println(result);
        System.out.println(resultOfNegate);
        System.out.println(andResult);
        System.out.println(orResult);
    }

    @Order(2)
    @Test
    public void predicateListTest() {
        List<Predicate<String>> predicateList = new ArrayList<>();
        predicateList.add(containsPredicate);
        predicateList.add(containsPredicate.negate());

        predicateList
            .forEach(predicate -> System.out.println(predicate.test("SW")));
    }

    @Order(3)
    @Test
    public void biPredicateTest() {
        //Calling BiPredicate functions.
        boolean result = containsBiPredicate.test("Hello SW Test Academy", "SW");
        System.out.println(result);
    }
}

Output

java predicate interface

Runnable Interface

The Runnable does not allow input, it does not return value. It is like a function without parameters, without return type. Let’s make it tangible with examples.

public class RunnableInterface {
    //Runnable function declarations.
    Runnable runFunction = () -> System.out.println("I am running!");

    @Test
    public void runnableTest() {
        //Calling Runnable functions.
        runFunction.run();
    }

    //Running Asynchronously
    public static void main(String[] args) {
        Runnable runFunction = () -> System.out.println("I am running!");

        Runnable runWithDelay = () -> {
            Uninterruptibles.sleepUninterruptibly(3000, TimeUnit.MILLISECONDS);
            System.out.println("I am running Asynchronously!");
        };

        new Thread(runWithDelay).start();

        runFunction.run();
    }
}

Test output:

runnable

Main Class output:

java runnable interface

Callable Interface

Callable does not allow input, it returns a value. It is like a function without parameters, with the return type. An example is as follows.

public class CallableInterface {
    //Callable function declarations.
    Callable<Double> callFunction     = () -> Math.random() * 100;
    Supplier<Double> supplierFunction = () -> Math.random() * 100;

    @SneakyThrows
    @Test
    public void callableTest() {
        //Calling functions.
        int callResult = callFunction.call().intValue();
        int getResult = supplierFunction.get().intValue();

        System.out.println(callResult);
        System.out.println(getResult);
    }
}

Output

java callable interface

GitHub Project

https://github.com/swtestacademy/java-functional/tree/main/src/test/java/functional/interfaces

Thanks for reading.
Onur Baskirt

1 thought on “Java Functional Interfaces with Examples”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.