A new concept introduced in Java 8, functional interfaces were added to support lambda expressions. Within this article, we’ll be covering what functional interfaces are and the reason why they were added to Java 8, as well as the benefits they provide.

An interface with only one abstract method is a functional interface. Essentially, it’s an interface with only one functionality, which is then implemented by the abstract method.

The following code demonstrates a functional interface:

@FunctionalInterface

public interface Interface1 {

public void method1();

}


The above example defines an interface called Interface1 and has only one abstract method (called method1).

Multiple Default and Static Methods

A functional interface can have more than one default OR static method. With this in mind, consider the following code:

@FunctionalInterface

public interface AnotherFunctionalInterface {

public void method1();

public default void method2(){

}

public static void method3(){

}

}

Above, the AnotherFunctionalInterface has one abstract method, one static method and one default method. It’s still a functional interface because it still has only one abstract method.

The @FunctionalInterface Annotation

You’ll be able to find a new annotation in Java 8 called ‘@FunctionalInterface’. Able to be specified on an interface, @FunctionalInterface marks a normal interface as a functional interface. When this annotation is specified on an interface, a compilation error will occur if the interface has more than one abstract method.

Look at the following example:

@FunctionalInterface

public interface Interface1 {

public void method1();

public void method2();

}

Here, Interface1 has two abstract methods: method1 and method2. Because of this, a compilation error will occur, but you can get rid of it by removing the @FunctionInterface annotation. Remember, if you do this, the interface will no longer be a functional interface.

The @FunctionalInterface annotation is optional. If it isn’t specified (but your interface has only one abstract method), the interface will still be treated as a functional interface. The annotation was added in the example above in order to force the compiler to check that there is only one abstract method.

The Need for Functional Interfaces

Because Java is an object-orientated language, it requires a lot of boilerplate code and can sometimes be a little verbose. To overcome this issue, Java 8 introduced lambda expressions. However, unlike functional programming languages, lambda expressions can’t be used on their own in Java because of their object-orientated nature. Functional interfaces were added to deal with this and support lambda expressions. Paired together, they help to write clean and concise code.

The following code demonstrates this:

public class MyClass {

public static void main(String args[]){

Interface1 obj = () -> System.out.println("In abstractMethod");

obj.method1();

}

}

In the example above, method1 in Interface1 is implemented via the specified lambda expression. If functional interfaces and lambda expressions didn’t exist, you’d need to instead create a class that implements Interface1 and overrides method1. Thankfully, the code in the example above (via a lambda expression) is far more concise.

In-Built Functional Interfaces in Java

Interfaces Pre Java 8

Interfaces were already available in Java, such as Runnable and Comparator, and already had a single abstract method before Java 8 came along. After Java 8, they were designed as functional interfaces via the @FunctionalInterface annotation.

As an example, beginning with Java 8, the Runnable interface can be implemented via a lambda expression, like so:

public class MyThread {

public static void main(String[] args) {

Runnable r = () -> System.out.println("In thread");

Thread t = new Thread(r);

t.start();

}

}

As stated previously, the Runnable interface is implemented via a lambda expression and the Runnable instance is passed to the Thread constructor in order to create a new thread. When the new Thread begins, the code within the lambda expression will be executed.

New Interfaces by Java 8

Java 8 has added a new package called java.util.Function that has several functional interfaces that can be used in various scenarios, with the subsequent sections explaining some of the interfaces available within this package.

The Predicate Interface

The predicate interface has a method called ‘test’ that accepts an argument of any data type and returns a boolean value. Essentially, it can be used to check if the input value satisfies a certain condition. The following sample of code demonstrates this:

Predicate<Integer> greaterThan8 = (input) -> input > 8;

System.out.println("4 is greater than 8 = "+greaterThan8.test(4));

Here, a predicate interface that accepts an Integer value is defined and is implemented via a lambda expression that checks whether the input value is greater than 8. This code will print the following output:

4 is greater than 8 = true

The Consumer Interface

The Consumer interface has a method called ‘accept’ that literally accepts an argument of any data type and only returns a void. This is commonly used to modify an input object or to process elements in either a Collection or a Stream. The following code demonstrates the Consumer interface:

Consumer<Integer> doubleInput = (num) -> System.out.println("Double of input is "+(num*2));

doubleInput.accept(8);


In the code above, you can see that a Consumer interface that accepts an Integer value is defined. Implemented via a lambda expression, it doubles the input value and prints it. This code will instead print the following output:

Double of input is 16

The Function Interface

The Function interface has a method called ‘apply’ that accepts an object of any datatype and returns a result of any datatype. This is generally used for modifying an input object, as well as converting an object of one type to an object of a different type.

The following code shows how this works:

Function<String,Integer> lengthChecker = (str) -> str.length();

System.out.println("Length is +lengthChecker.apply("Hello World"));

Above, the Function interface that accepts a String as an input and returns an Integer as an output is defined and is implemented via a lambda expression that returns the length of the input String. The code will print the following output:

Length is 11

The Supplier Interface

The Supplier Interface has a method called ‘get’ which doesn’t accept any parameters but instead returns an object of any datatype. This interface is most likely to be used for generating data:

Supplier<Date> dateGenerator = () -> new Date();

System.out.println("Date is "+dateGenerator.get());

From the example above, we can see that the Supplier interface which returns a Date object is defined. Implemented via the lambda expression that creates a new Date object (as well as returns it), the code will print the following:

Date is Thu Aug 15 10:44:04 IST 2019

Why Use Functional Interfaces?

To Keep Code Concise

As mentioned earlier, functional interfaces and lambda expressions eliminate boilerplate code related to implementing an interface. As well as this, they help make the code both concise AND readable.

forEach Support

The in-built functionality interface (otherwise known as ‘Consumer’) is used by the forEach method added by Java 8, and helps to internally iterate over a Collection:

List<Integer> inputList = Arrays.asList(10,14,25,78);

inputList.forEach((num) -> System.out.println("Number is "+num));

Above, a list of integers is created. The forEach method accepts a Consumer instance and applies the lambda expression used for the Consumer interface to each element in the list. Here, a lambda expression that simply prints the input is specified, so the code will result in all the elements in the list being printed to the console.

Stream Support

In-built functional interfaces like Consumer, Predicate, Function and more are used in the Stream API. A powerful feature added by Java 8, the Stream API can be used to apply some operations to the elements in a collection or array.

The Stream Filter Operation

The filter operation uses the Predicate interface and can be used to filter an input stream based on a specified condition:

List<Integer> input = Arrays.asList(5, 3, 11, 15, 9, 2, 5, 11);

Stream<Integer> stream = input.stream().filter(num -> num >= 9);

The code above uses the stream.filter method to filter out the element in an input list that is greater than 9. What’s more, it accepts a Predicate instance (which is implemented via a lambda expression). Applying the lambda expression to every element in the list, the output stream will only have numbers greater than 9.

The Stream Map operation

The map operation can be used to apply some function to all the elements in an input stream. Note that it uses the Function interface. The following code demonstrates how it works:

List<String> animals = Arrays.asList("cat","dog","horse");

animals.stream().map(str->str.toUpperCase());

Using the stream.map operation to convert each String in the input list to uppercase, the map operation accepts a Function instance which is implemented via a lambda expression. Applying the lambda expression to each element in the list, the output will be a stream with uppercase Strings.

Functional interfaces, as well as lambda expressions, are Java’s first step into functional programming. Eliminating any boilerplate code that arises due to the object-orientated nature of Java, they both help to keep your code concise and are definitely worth using.

Like what you’ve read? Make sure to check out our other Java articles and tutorials.