Functional Interface Explained in Detail Introduced From Java 8
In this article, explore functional interface introduced in Java 8.
Join the DZone community and get the full member experience.
Join For FreeOriginally published August 2020
Functional interfaces are introduced as part of Java 8. It is implemented using the annotation called @FunctionalInterface
. It ensures that the interface should have only one abstract method. The usage of the abstract keyword is optional as the method defined inside the interface is by default abstract. It is important to note that a functional interface can have multiple default methods (it can be said concrete methods which are default), but only one abstract method. The default method has been introduced in interface so that a new method can be appended in the class without affecting the implementing class of the existing interfaces. Prior to Java 8, the implementing class of an interface had to implement all the abstract methods defined in the interface.
The functional interface has been introduced in Java 8 to support the lambda expression. On the other hand, it can be said lambda expression is the instance of a functional interface.
For example:
Interface Area<T,S,R>
{
R calculateArea(T a, S b);
}
It is well known how the above interface is implemented prior to Java 8, but in Java 8, it can be implemented in a smarter way as in the implementation below.
xxxxxxxxxx
static Area<Double, Double, Double> area = (a, b) ->(a*b);
System.out.println("The total area is "+ area.calculateArea(1.1,1.1));
The full implementation of the above functional interface is available on GitHub, the link of which will be provided at the end of this tutorial.
In Java 8, there are 4 main functional interfaces are introduced which could be used in different scenarios. These are given below.
Consumer
Predicate
Function
Supplier
Among the above four interfaces, the first three interfaces also have extensions also which are given below.
Consumer
-BiConsumer
Predicate
–BiPredicate
Function
–BiFunction
,UnaryOperator
,BinaryOperator
1. Consumer
Let’s discuss Consumer
.
- The
Consumer
interface accepts one argument, but there is no return value. - The name of the function inside this interface is
accept
.
xxxxxxxxxx
public interface Consumer<T> {
void accept(T t);
…
}
Consumer<String> ucConsumer = (s) -> System.out.println(s.toUpperCase());
ucConsumer.accept("consumer");
Output
The above consumer is accepting one argument, and printing it in upper case, but there is no return value.
BiConsumer
The extension of the Consumer
, which is BiConsumer,
accepts two arguments and returns nothing.
xxxxxxxxxx
public interface BiConsumer<T, U> {
void accept(T t, U u);
...
}
BiConsumer<String, String> biConsumer = (x,y) -> {
System.out.println(" x : " + x + " y : " + y );
};
biConsumer.accept("Sun" , "Moon");
Output: x: Sun y: Moon
2. Predicate
Predicate
will accept one argument, do some processing, and then return boolean
.
xxxxxxxxxx
public interface Predicate<T> {
boolean test(T t);
...
}
static Predicate<Integer> p = (i) -> {return i%2 ==0;};
System.out.println("Result is p : " + p.test(4));
Output
True
BiPredicate
Instead of one argument, BiPredicate
will accept two arguments and return boolean
.
3. Function
This interface accepts one argument and returns a value after the required processing. It is defined as below. The required processing logic will be executed on the invocation of the apply
method.
xxxxxxxxxx
public interface Function<T, R> {
R apply(T t);
…..
}
For example, the above functional interface will be executed while the following code would be executed.
xxxxxxxxxx
static Function<String,String> upperCase = (name) -> name.toUpperCase();
System.out.println("Result is : " + upperCase.apply("functional"));
Output
functional
In the above example, the Function
is accepting one string and also returns one string.
BiFunction
The BiFunction
is similar to Function
except it accepts two inputs, whereas Function
accepts one argument. The sample code for the BiFunction
interface is given below. In the below interface code T
and U
are the inputs and R
is the single output.
xxxxxxxxxx
public interface BiFunction<T, U, R> {
R apply(T t, U u);
……
}
UnaryOperator and BinaryOperator
Another two interfaces are UnaryOperator
and BinaryOperator
,which extends the Function
and BiFunction
respectively. The following interface code snippets are given below for these two interfaces.
xxxxxxxxxx
public interface UnaryOperator<T> extends Function<T, T> {
…
}
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
...
}
From the above interfaces, it is easy to understand that the UnaryOperator
accepts a single argument and return a single argument, but both the input and output argument should be of same or similar type.
On the other hand, BinaryOperator
accepts two arguments and returns one argument similar to BiFunction
, but the type of all the input and output argument should be of similar type.
The following examples for UnaryOperator
and BinaryOperator
interfaces are given respectively.
xxxxxxxxxx
UnaryOperator<String> unaryOperator = (s)->s.concat("Operator");
System.out.println(unaryOperator.apply("Unary"));
Output
UnaryOperator
xxxxxxxxxx
BinaryOperator<Integer> binaryOperator = (a,b) -> a+b;
System.out.println(binaryOperator.apply(3,4));
Output: 7
4. Supplier
Supplier
functional interface does not accept any input; rather returns a single output. The following interface code is given for understanding.
xxxxxxxxxx
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
Find the below example:
xxxxxxxxxx
public static Supplier<String> helloWorld = () -> {
return "Hello World";
};
xxxxxxxxxx
String greeting = helloWorld.get();
System.out.println("The greeting message: "+greeting)
The above is just a simple example but it may be used in a complex business scenario. For all the above scenarios download the sample code from here.
Opinions expressed by DZone contributors are their own.
Comments