Java Interview Questions (series) - Common Functional Interfaces

80% of the time you use 20% of the @FunctionalInterfaces 👋

Brief

🤔  In a previous post, we spoke about the @FunctionalInterface feature coming with Java 8.

We understood:

🔹 What is it used for?

🔹 What is it made of?

🔹 How you can create your own?

Java developers also created some out of the box functional interfaces to make our lives easier. 💯

In this post, we'll go through the most used ones, what are they used for and how to use them on your particular use-case. 🚀

Implementation

🔵   Predicate

Functional interface whose abstract method accepts a single element and returns a boolean.

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T input);
}

Remembering from the previous post on functional interfaces, we need to provide an implementation for the test abstract method.

🔸   Example

Let's create a predicate which determines if a given integer is even or odd.

Predicate<Integer> isEvenPredicate = input -> input%2 == 0;  // provide implementation through a lambda expression

To use it, you only need to call its test method.

boolean res1 = isEvenPredicate.test(4);   // will return true

boolean res2 = isEvenPredicate.test(5);   // will return false;


🔵   Consumer

Functional interface whose abstract method accepts a single element and returns void.

@FunctionalInterface
public interface Consumer<T> {
    void accept(T input);
}

🔸   Example

Let's say you want to print the element on the console.

Consumer<Integer> loggingConsumer = input -> System.out.println(input);  // provide implementation through a lambda expression

And then, to apply it, you'll call the accept method.

loggingConsumer.accept(2);  // will print to the console: "2"


🔵   Supplier

Functional interface whose abstract method doesn't have any input parameters but returns a value.

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

🔸   Example

A use-case for this one might be to generate a random number.

Supplier<Double> randomSupplier = () -> Math.random();   // provide implementation through a lambda expression

To use it, you'll use the get method.

Double random = randomSupplier.get();


🔵   Function

Functional interface whose abstract method accepts a single element as an input and also returns a value.

@FunctionalInterface
public interface Function<T, U> {
    U apply(T input);
}

In other words, it's a combination of Consumer and Supplier.

This is the one you should use when you want to change the state of the elements, or generate a new type of object based on the input.

🔸   Example

You may have a User object with a name property.

Given a string provided as input for name, with Function, you can create a User with that particular name.

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }
}
User.java
Function<String, User> userFunction = input -> new User(input); // provide implementation through a lambda expression

User user = userFunction.apply("John Doe"); // generates a user with "John Doe" for name.


Bonus

Each of these functional interfaces that we just discussed, also have a correspondent which can accept 2 input parameters of different types.

Except the Supplier one, of course, as it can't receive any parameter.

🔹   BiConsumer<T,U> - accepts one T parameter and one U parameter and returns void.

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
}
BiConsumer.java
BiConsumer<Integer, String> loggingConsumer = (input1, input2) -> System.out.println(input1 + input2);

loggingConsumer.accept(12, "apples"); // will print to the console: "12 apples"
Example

🔹   BiFunction<T,U,R> - accepts one T parameter and one U parameter and returns a R parameter.

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}
BiFunction.java
public class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}
User.java
Function<String, Integer, User> userFunction = (name, age) -> new User(name, age); // provide implementation through a lambda expression

User user = userFunction.apply("John Doe", 23); // generates a user with "John Doe" for name and "23" for age.
Example

🔹   BiPredicate<T,U> - accepts one T parameter and one U parameter and returns a boolean.

@FunctionalInterface
public interface BiPredicate<T, U> {
    boolean test(T t, U u);
}
BiPredicate<Integer, Integer> comparePredicate = (number1, number2) -> number1 >= number2;

comparePredicate.test(1,2); // returns false

comparePredicate.test(3,1); // returns true
Example


💡
Don't miss out on more posts like this! Susbcribe to our free newsletter!
💡
Currently I am working on a Java Interview e-book designed to successfully get you through any Java technical interview you may take.
Stay tuned! 🚀