Introduction to functional interfaces
A functional interface is an interface that has a single abstract method. Functional interfaces can have multiple static and default methods, but they should have only one abstract method to qualify as a functional interface. Functional interfaces were introduced in Java 8 in order to implement lambda expressions.
Code Sample
The following code defines a functional interface called DemoFunctionalInterface.
package demo;
@FunctionalInterface
public interface DemoFunctionalInterface {
public void myAbstractMethod();
public default void myDefaultMethod() {
System.out.println("Default method");
}
public static void myStaticMethod() {
System.out.println("Static method");
}
}
So here, we have declared an interface called DemoFunctionalInterface. It has an abstract method called myAbstractMethod. Since there is only one abstract method present, this is a functional interface.
In addition to the abstract method, this functional interface has two additional methods, one is a static method and the other is a default method.
@FunctionalInterface annotation
In the code above, the @FunctionalInterface annotation is specified on the interface. This marks the interface as a functional interface and forces the compiler to check that there is only one abstract method. So if this annotation is specified and if we try to add another abstract method, there will be a compilation error as can be seen in the following code:
@FunctionalInterface
public interface DemoFunctionalInterface {
public void myAbstractMethod();
//the following method causes compilation error
public void anotherMethod();
}
The @FunctionalInterface annotation is optional. So if this annotation is removed, the compilation error goes away, but the interface will no longer be a functional interface.
In general, any interface with a single abstract method is treated as a functional interface by Java. Irrespective of whether you use @FunctionalInterface annotation or not.
Uses of Functional interfaces
Java 8 introduced lambda expressions as the first step into functional programming. Using lambda expressions, you can provide inline implementations for functional interfaces. This helps make the code concise and gets rid of the boilerplate code.
Suppose you need to implement the DemoFunctionalInterface declared above. Prior to Java 8, you would need to write code similar to the following:
public class DemoClass implements DemoFunctionalInterface{
@Override
public void myAbstractMethod() {
System.out.println("In myAbstractMethod");
}
public static void main(String args[]){
DemoClass obj = new DemoClass() ;
obj.myAbstractMethod();
}
}
So basically you need to do the following:
- Create a class that implements the interface
- Override the abstract method
- Write the code that invokes the implemented method
So this seems like a lot of code to implement a single method. Java 8 solves this issue via functional interfaces and lambda expressions. So we can re-write the code above as follows:
public class DemoClass2 {
public static void main(String[] args) {
DemoFunctionalInterface obj = () -> System.out.println("In myAbstractMethod");
obj.myAbstractMethod();
}
}
Compared to the pre-Java 8 code, this code is quite concise. A lambda expression is used to implement the myAbstractMethod. So, you do not need to create a class that implements the interface and you do not need to provide a method body.
When you run this code, it will print the same output as before:
method1
In-Built Functional Interfaces in Java
Prior to Java 8, there were already interfaces like Runnable, Comparator, etc with a single abstract method. After Java 8, they were designated as functional interfaces via the @FunctionalInterface annotation.
In addition, Java 8 has added a new package called java.util.Function. It has a number of functional interfaces that can be used to perform some functions.
Let us see how some of these can be implemented.
Function Interface
A function has a single method called apply. It accepts an object of any data type and returns a result of any datatype.
Consider the following code:
import java.util.function.Function;
public class MyFunctionDemo {
public static void main(String[] args) {
Function<Integer,Double> squareRootGenerator = (input) -> {return Math.sqrt(input);};
int input = 16;
double result = squareRootGenerator.apply(input);
System.out.println("Square Root of "+input+" is "+result);
}
}
Code Explanation
This Function implementation accepts an integer number and returns its square root. Function<Integer, Double> specifies that the input is of type Integer, and output is of type Double. The lambda expression needs to be assigned to an instance of the functional interface, in this case, squareRootGenerator Finally, the squareRootGenerator.apply method is invoked which uses the code in the lambda expression to print the output.
So when this code is executed, it will print the following output:
Square Root of 16 is 4.0
Supplier Interface
The supplier has a single method called get. It does not accept any arguments. It returns an object of any data type.
public class MySupplierDemo {
public static void main(String[] args) {
Supplier<Integer[]> randomNumberGenerator = () -> {
Integer[] result = new Integer[5];
for(int i=0; i< result.length;i++){
result[i] = new Random().nextInt(100);
}
return result;
};
System.out.println("Result:");
Integer[] result = randomNumberGenerator.get();
for(int num:result)
System.out.println(num);
}
}
This Supplier returns an Integer array of 5 random numbers. As before, the get method is implemented via a lambda expression which creates an Integer array of length 5 and populates it with 5 random numbers.
So when this code is executed it will print output similar to the following:
Result:
53
78
19
89
16
Consumer Interface
The consumer has a single method called accept. It accepts a single argument of any data type and does not return any result. So it modifies the input object.
Consider the code below:
public class Person {
private String name;
private int age;
Person(String name,int age){
this.name=name;
this.age=age;
}
// Getter and setter methods
}
import java.util.function.Consumer;
public class MyConsumerDemo {
public static void main(String[] args) {
Consumer<Person> ageIncreaser = (input) -> {
int newAge = input.getAge()+5;
input.setAge(newAge);
};
Person person = new Person("abc",25);
ageIncreaser.accept(person);
System.out.println("New age:"+person.getAge());
}
}
A Person class is declared with name and age fields. This Consumer accepts a Person object and increases the age of the person by 5.
When this code is executed, it will print the following output:
New age:30
Conclusion
Functional interfaces and lambda expressions provide a totally new way of programming for Java developers. They help in getting rid of the boilerplate code involved in implementing an interface and make the code concise.