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 expressionTo 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 expressionAnd 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;
}
}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<Integer, String> loggingConsumer = (input1, input2) -> System.out.println(input1 + input2);
loggingConsumer.accept(12, "apples"); // will print to the console: "12 apples"🔹 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);
}public class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}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.🔹 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 trueStay tuned! 🚀