Functional Interfaces in a nutshell for Java developers

Piyush Aggarwal
5 min readFeb 13, 2021

--

Functional Interfaces were introduced in Java 8. These interfaces are called functional because they basically act as a function. Mostly used in streams and CompletableFutures APIs in Java

Lambda expressions help in writing cleaner functional interfaces code

Let’s see some of the properties of all the functional interfaces in Java

→ Every functional interface has at least one non-default function and only one abstract function in which we are supposed to write our code either using lambdas or using anonymous classes(in this article we will see how to write functional interfaces using anonymous classes)

→ If there is more than one non-default function, then that must be overriding some function of a base class. For example, in the case of the Comparator interface, we have 2 non-default functions equals and compare

@FunctionalInterface
public interface Comparator<T> {

int compare(T o1, T o2);


boolean equals(Object obj);
}

In this example only compare is an abstract function that we definitely need to define while creating a comparator interface but equals is an overridden function from java.lang.Object class which we can override if we wish

→ Some functional interfaces also have default functions. Default functions are nothing but the functions which have an implementation in the interface itself (yes this is possible ≥ Java 1.8)

→ Every functional interface will have the @FunctionInterface annotation over it

Now let’s look at some of the most popular functional interfaces in java and try to understand the structure of these (i.e, what it takes and what it returns)

1. Consumer

As the name suggests, this functional interface just consumes the data and does not produce anything but can do any operation on the data being consumed. The structure of it is as below:

@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
*
@param t the input argument
*/
void accept(T t);
}

So consumer functional interface takes in generic object T and the accept function is the one that executes the code for this interface.

The below example consumes an integer and prints the double of that number using stdout

Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer * 2);
}
};

consumer.accept(6);

Output is → 12

2. Supplier

As the name suggests, this functional interface just produces/supplies some data and does not consume anything. The structure of it is as below:

@FunctionalInterface
public interface Supplier<T> {

/**
* Gets a result.
*
*
@return a result
*/
T get();
}

So supplier functional interface returns a generic object T and the get function is the one that executes the code for this interface

The below example returns a random integer

Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return new Random().nextInt();
}
};

System.out.println(supplier.get());

Output is → -56000

3. Function

Function is a functional interface that takes in some input data and returns some output data by performing some operation on the input data. It's a combination of consumer and supplier. The structure of it is as below:

@FunctionalInterface
public interface Function<T, R> {
R apply(T var1);
}

So Function functional interface of T, R states that the input has the data type of T and the output has the data type of R, now T may be equal to R or may not be. It has an apply function in which the code of this interface executes.

The below examples takes in a number and return the string which is a combination of that number and “test” appended after that number

Function<Integer, String > function = new Function<Integer, String>() {
@Override
public String apply(Integer integer) {
return "" + integer + "test";
}
};

System.out.println(function.apply(10));

Output is → 10test

4. Predicate

As the name suggests, this functional interface takes in input data and returns the boolean result (true or false). The structure of it is as below:

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

So Predicate functional interface takes in an object and we can perform any test operation in the test function which finally returns a boolean result.

The example below takes a string in the argument and returns whether it is having the character ‘a’ in it or not.

Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("a");
}
};

System.out.println(predicate.test("australia"));

Output is → true

Let us also look at one of the default function as well of the Predicate interface

default Predicate<T> and(Predicate<? super T> var1) {
Objects.requireNonNull(var1);
return (var2) -> {
return this.test(var2) && var1.test(var2);
};
}

Since this a default function, we can call it on some predicate only and this function takes in another Predicate in the argument, and in the end, it returns a predicate which is the AND combination of both the predicates

The below examples returns true if a string contains both a and b (although we could have done it simply using 1 predicate but just to show the functioning of and function we are using 2 predicates here)

Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("a");
}
};

Predicate<String> predicate2 = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("b");
}
};

System.out.println(predicate.and(predicate2).test("australia"));

Output is → false (since australia does not contain ‘b’)

5. Runnable

As the name suggests, this functional interface doesn't consume or produce anything, that’s why there is no type T as generic in the definition of this interface, it just runs some piece of code defined in the run function of this interface. The structure of it is as below:

@FunctionalInterface
public interface Runnable {
void run();
}

The below examples prints the sum of the first 10 natural numbers

Runnable runnable = new Runnable() {
@Override
public void run() {
int sum = 0;
for(int i=1;i<10;i++){
sum += i;
}

System.out.println(sum);
}
};

runnable.run();

Output is → 45

Runnable is also used while creating Threads

6. BiConusmer

This functional interface takes in two types of objects in its definition of type T and U, now they may be the same or may not be and it acts similar to the consumer interface, consumes the data does not produce/return anything. The structure of it is as below:

@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T var1, U var2);
}

The accept function can do anything that we want but it won’t return anything.

The below example just finds out whether a given place is present in the list of places or not.

BiConsumer<String, List<String>> biConsumer = new BiConsumer<String, List<String>>() {
@Override
public void accept(String s, List<String> strings) {
if(strings.contains(s)){
System.out.println(s + " is present in the list");
}else{
System.out.println(s + " is not present in the list");
}
}
};

biConsumer.accept("delhi", Arrays.asList("china","delhi", "austria", "india"));

Output is → delhi is present in the list

The only difference b/w Consumer and BiConsumer is that BiConsumer takes two objects in the argument but Consumer takes only one

Similarly, there are many other types of functional interfaces in Java, but these are the most popular ones we use in streams API and in CompletableFutures as well.

Lastly, I would like to conclude that all the functions that were written in the article can be reduced to just 1–2 lines of code using lambdas (they are a modern way of writing anonymous classes) which we will see in the next article.

Thank you for reading the article, feel free to comment below in case of any doubts or suggestions for the next articles :)

--

--

Piyush Aggarwal
Piyush Aggarwal

Written by Piyush Aggarwal

Software Developer who likes to work in fast paced start up with sound knowledge of backend technologies and infrastructure.

Responses (1)