Functional Interface in Java

In this article, we will learn about functional interface in java. Let’s start!!!

What is the functional interface in java?

Interfaces that comprise only one abstract method are called functional interfaces. Though there can be many static and default methods, it can contain only a single abstract method.

Since Java 8, lambda expressions are used to denote objects of a functional interface. The functional interface has only one functionality to present. It is also termed as Single Abstract Method Interface or SAM Interface. This feature helps to attain a good functional programming approach in Java.

Java consists of various functional interfaces like ActionListener, Runnable, Comparable Interfaces etc.

Defining functional interface in java

To implement functional interfaces in Java, we need to follow an important rule. It should contain only one abstract method and can have various static and default methods. Here is a snippet as an example:

public interface FirstCodeInterface{
public void method1();
}

Here, we can observe that the FirstCodeInterface consists of only one method, method1.

Implementation of Functional Interfaces using Lambda Expression in Java:

Through lambda expressions, we can view the functional programming features in an object-oriented environment. As objects are closely associated functions, Java provides lambda expressions to use with functional interfaces.

Java Lambda Expression Syntax:

java lambda expression syntax

Lambda operator -> body

Here, the lambda operator can be:

  • Zero Parameter

{} -> System.out.println(“Zero Parameter lambda”);

  • One Parameter

{p} -> System.out.println(“One Parameter:” +p};

  • Multiple Parameters

{p1, p2}-> System.out.println(“Multiple parameters:” +p1+ “,”+p2);

Examples of Java Lambda Expressions:

Here are a few lambda expressions’ code snippets along with a brief explanation:

Sl.no Lambda expression Description
1 () -> {} No parameters, the result is void
2 () -> 42 No parameters, expression body
3 () -> null No parameters, expression body
4 () -> {return 42;} No parameters, body is blocked with return
5 () -> {System.gc;} No parameters, void block body
6 (int x) -> x+1 Single declared-type argument
7 (int x) -> {return x+1;} Single declared-type argument with return
8 (x) -> x+1 Single inferred-type argument
9 x -> x+1 Parentheses optional for single inferred-type case
10 (String s) -> s.length() Single declared-type argument
11 (Thread t) -> {t.start();} Single declared-type argument
12 s-> s.length() Single declared-type argument
13 t -> {t.start();} Single declared-type argument
14 (int x, int y) -> x+y Multiple declared-type parameters
15 (x,y) -> x+y Multiple inferred-type parameters
16 (x, final y) -> x+y Illegal: Cannot modify inferred-type parameters
17 (x, int y) -> x+y Illegal: Cannot mix inferred and declared types

Importance of Lambda Expression:

The presence of lambda expression is an advantage for the programmers in various means. Here are a few reasons why lambda expressions are required.

  • Minimum lines of code:

These expressions reduce the lines of code to a great extent. Creating objects for functional interfaces using lambda expression is easier than using a random class for the same.

  • Sequential and parallel execution support:

We can obtain the provision for a Stream API sequential and parallel execution.

  • Passing behaviors into methods:

To pass the behavior of a method, lambda expressions can be of great use. Here is a small example to demonstrate it:

public static int conditionCalc(List <Integer> number, Predict <Integer> predicate){
return numbers.parallelstream().filter(predicate).mapToInt(i -> i).sum();
}

For Example

conditionCalc(numbers, n -> true) // sum of all numbers
conditionCalc(numbers, i ->i%2==0) //sum of all even numbers
consitionCalc(numbers, i->i<10) // sum of all numbers less than 10
  • Higher efficiency with laziness:

These lambda expressions help us achieve lazy evaluation with increased efficiency. Let us look at an example to understand it better.

The sample code below aims to find the maximum odd number in the range of 10 to 50 and returns the cube of it.

public static int findCudeofMaxOdd(List < Integer > numbers){
return number.stream().filter(NumberTest::isOdd)
.filter(NumberTest::isGreaterThan10)
.filter(NumberTest::isLessThan50)
.max(Comparator.naturalOrder()).map(i->i*i*i).get();
}
public static boolean isOdd(int i){
return i%2!=0;
}
public static boolean isGreaterThan10(int i){
return i > 10;
}
public static boolean isLessThan50(int i){
return i < 50;
}

Java Functional interface example: using anonymous inner class vs using a lambda expression:

The functional interface was used even before the arrival of Java 8. This took place by creating anonymous inner classes using functional interfaces. These functional interfaces are Runnable, ActionListener, Comparator, etc. They all have only one abstract method.

Example program using anonymous inner class before Java 8:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class FirsstCode extends JFrame{
JButton b;
public FirstCode(){
displayTitle(“Button Action example without Lambda Expression”);
setSize(100, 250);
setVisible(true);
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
b= new JButton(“Button”);
b.setBounds(200, 300, 100, 30);
b.addActionListener(new ActionListener()){
public void actionperformed(ActionEvent e){
System.out.println(“The button is clicked”);
}}
};
add(b);
}
public static void main(String args[]){
new FirstCode();
}
}

ActionListener Example: Lambda Expression after Java 8:

import javax.swing.*;
import java.awt.*;
class FirstCode extends JFrame{
JButton b;
public FirstCode(){
displayTitle(“Button Action Example using Lambda Expression ”);
setSize(100, 250);
setVisible(true);
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
b= new JButton(“Button”);
b.setBounds(200, 300, 100, 30);
b.addActionListener(e -> System.out.println(“The Button is clicked”));
add(b);
}
public static void main(String args[]){
new FirstCode();
}
}

@FunctionalInterface Annotation:

Using the @FunctionalInterface annotation guarantees that there is no more than a single abstract method in the functional interface.

If there is more than one abstract method in the functional interface, the compiler throws an “Unexpected @FunctionalInterface annotation” message. If you are planning to use only one abstract method, you need not use this annotation.

Sample program to implement the @FunctionalInterface Annotation in Java:

@FunctionalInterface
interface Cubecalc{
int calculateCube(int c);
} 
public class FirstCode{
public static void main(String args[]){
int n = 5;
Cubecalc cc = (int c) -> x * x;
int soln = cc.calculateCube(n);
System.out.println(“The cube of the number is: ”+soln);
}
}

Output:

The cube of the number is: 125

Primitive Function Specializations:

The primitive types cannot be used as generic type arguments. Thus, the functional interface contains certain versions of the int, double and long data types with various combinations and return types.

  • IntFunction, LongFunction, DoubleFunction: The arguments belonging to these types are accepted, and the return type is parameterized.
  • ToIntFunction, ToLongFunction, ToDoubtleFunction: They accept parameterized arguments and the return type is of the mentioned type.
  • DoubleToIntFunction, DoubleToLongFunction, IntoToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction: They contain argument and the return type as primitive types as mentioned in their names.

Two Arity Function Specializations:

In case we require two arguments, we can use additional interfaces to define such lambdas. These functions start with the prefix keyword ‘Bi’ in their names. Some examples are BiFunction, ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction.

Prototype and syntax of Bi-Function:

@FunctionalInterface
public interface BiFunction<A, B, C>{
C apply(L l, V v);
……..
}
Sample snippet to add marks for a particular person using BiFunction:
Map<String Integer> income = new HashMap<>();
marks.put(“Nina”, 85);
marks.put(“Hira”, 49);
marks.put(“Martin”, 90);
marks.replaceAll(name, oldMarks) -> name.equals(“Hira”)? oldMarks+24);

Legacy Functional Interfaces in Java:

Though Java 8 has contributed a lot to the concept of a functional interface, it is not the only version to do so. There are many previous versions that also led to the development of this concept and the usage of lambdas.

One such important is the Runnable and Callable interfaces used in APIs. Java 8 also lets us denote these interfaces with a @FunctionalInterface annotation.

Java Pre-defined Functional Interfaces:

Interface Description
BiConsumer<T,U> It represents an operation that accepts two input arguments and returns no value.
Consumer<T> It represents an operation that accepts a single argument and returns nothing.
Function<T,R> It represents a function that accepts one argument and returns a value.
Predicate<T> It represents a predicate of one argument.
BiFunction<T,U,R> It represents a function that accepts two arguments and returns a result.
BinaryOperator<T> It represents an operation upon two operands of the same data type. The result is of the same type as the operands.
BiPredicate<T,U> It represents a predicate of two arguments.
BooleanSupplier It represents a supplier of boolean-valued values.
DoubleBinaryOperator It represents an operation upon two double type operands and returns a double type result.
DoubleConsumer It represents an operation that accepts a single double type argument and returns no result.
DoubleFunction<R> It represents a function that accepts a double type argument and produces a result.
DoublePredicate It represents a predicate of one double type argument.
DoubleSupplier It represents a supplier of double type results.
DoubleToIntFunction It represents a function that accepts a double type argument and produces a result of int data type.
DoubleToLongFunction It represents a function that accepts a double type argument and produces a result of long data type.
DoubleUnaryOperator It represents an operation on a single double type operand that produces a result of double data type.
IntBinaryOperator It represents an operation upon two int type operands and returns a result of int data type.
IntConsumer It represents an operation that accepts a single integer argument and returns nothing.
IntFunction<R> It represents a function that accepts an integer argument and returns a result.
IntPredicate It represents a predicate (boolean-valued function) of one integer argument.
IntSupplier It represents a supplier of integer data type.
IntToDoubleFunction It represents a function that accepts an integer argument and returns a double value.
IntToLongFunction It represents a function that accepts an integer argument and returns a value of long data type.
IntUnaryOperator It represents an operation on a single integer operand that produces a result of integer datatype.
LongBinaryOperator It represents an operation upon two long type operands and returns a long type result.
LongConsumer It represents an operation that accepts a single long type argument and returns nothing.
LongFunction<R> It represents a function that accepts a long type argument and returns a result.
LongPredicate It represents a predicate (boolean-valued function) of one long type argument.
LongSupplier It represents a supplier of long type results.
LongToDoubleFunction It represents a function that accepts a long type argument and returns a result of double type.
LongToIntFunction It represents a function that accepts a long type argument and returns a result of integer data type.
LongUnaryOperator It represents an operation on a single long type operand that returns a result of long data type.
ObjDoubleConsumer<T> It represents an operation that accepts an object and a double argument, and returns no result.
ObjIntConsumer<T> It represents an operation that accepts an object and an integer argument. It returns no result.
ObjLongConsumer<T> It represents an operation that accepts an object and a long argument, it returns no result.
Supplier<T> It represents a supplier of results.
ToDoubleBiFunction<T,U> It represents a function that accepts two arguments and produces a double type result.
ToDoubleFunction<T> It represents a function that returns a double type result.
ToIntBiFunction<T,U> It represents a function that accepts two arguments and returns an integer.
ToIntFunction<T> It represents a function that returns an integer.
ToLongBiFunction<T,U> It represents a function that accepts two arguments and returns a result of long type.
ToLongFunction<T> It represents a function that returns a result of long type.
UnaryOperator<T> It represents an operation on a single operand that returns a result of the same type as its operand.

Built-in Functional Interfaces in Java:

Apart from creating user-defined interfaces, there are various built-in functional interfaces that Java provides. The commonly used built-in functional interfaces in Java are present in the Java.util.function package is discussed in this section.

Built-in functional interfaces in java.util.function package:

The Built-in Java Functional interfaces are annotated with @FunctionalInterface. These interfaces are:

  • Runnable -> This interface contains only the run() method
  • Comparable -> It contains the compareTo() method.
  • ActionListener -> It contains the actionPerformed() method.
  • Callable -> It contains the call() method.

1. Function:

The most commonly used functional interface is the java.util.function.Function interface. It denotes a method with one argument and returns that single value.

Defining the Function interface:

public interface Function <T.R>{
public <R> apply(T parameter);
}

Bi-function:

The Bi-Function is considerably related to a Function. The only difference is that it takes two arguments, while a Function takes only one argument.

Prototype and syntax of Bi-Function:

@FunctionalInterface
public interface BiFunction<T, U, R>
{
R apply(T t, U u);
…….
}

Here, T and U are inputs and R is the output.

2. Predicate:

To denote a simple function, we often use the Java.util.function.Predicate. It takes one value as an argument and returns a boolean value, either true or false.

Defining functional interface:

public interface Predicate{
boolean test(T t);
}

Sample code to implement Predicate interface in Java:

import java.util.*;
import java.util.function.Predicate;
class FirstCode{
public static void main(String args[]){
List <String> courses = Arrays.asList(“Java”, “C”, “Python”, “C++”);
Predicate <String> p = (s) -> s.startsWith(“C”);
for(String str: courses){
if(p.test(str))System.out.println(str);
}
}
}

Output:

C
C++

Bi-Predicate:

It is an extension of the Predicate functional interface. It accepts two arguments to process and returns a boolean value. whereas, Predicate on the other side accepts only one value.

Syntax of Predicate Functional Interface:

public interface Predicate<T>{
boolean test(T t);
}

3. Unary operator:

To denote an operation, we use the Java.util.function.UnaryOperation interface in Java. It takes only one operator and returns an argument of the same type.

Sample snippet to implement Java UnaryOperator:

UnaryOperator <Person> unaryOperator = (person) -> {
course.name = “Course name”;
return course;
};

4. Binary operator:

The Binary operator interface in Java allows the presence of two parameters that returns a single value. A condition that prevails here is that both the parameters and the return type of the function should be of the same type.
We can use this operator to perform operations like addition, subtraction, multiplication, division and so on.

Example snippet to implement BinaryOperator interface:

BinaryOperator <MyValue> binaryOperator = (value1, value2) -> (value1, value2){
value1.add(value2);
return value1;
};

5. Supplier:

This interface represents the functions that supply the values. It is also termed a factory interface.

A sample implementation of Java Supplier Interface:

Supplier <Integer> supplier = () -> new Integer((int) (Math.random()*1000D));

6. Consumer:

The java.util.function package contains a Consumer interface that denotes a function. This function takes a value and returns nothing. It is mostly used in areas where we need to write a value to a file or over a network.

Sample implementation of the Java Consumer interface:

Consumer <Interger> consumer = (value) -> System.out.println(value);

Bi-Consumer:

When the consumer interface accepts only one argument, the Bi-Consumer interface accepts two arguments. Both, the Consumer and Bi-consumer have no return value. It plays a major role in iterating through entries of the map.

Important points:

1. In functional interfaces, only one abstract method is supported. Yet, by not using the @FunctionalInterface within a functional interface, more than one abstract method can be declared. In this case, it is no more a functional interface, it is known as a non-functional interface.

2. The @FunctionalInterface is voluntary, therefore it requires no annotation. To check its helping tendency with the compiler level, we can use it. but mostly it is optional.

3. A limitless count of static or default methods can be added to the functional interface.

4. Overriding the methods from the parent class does not disrupt any rules of a functional interface concept in Java.

5. The java.util.function package contains multiple built-in functional interfaces in Java 8.

Conclusion

These are the various functional interfaces available in Java. You can try out these sample programs and snippets to observe how these interfaces work.

Leave a Reply

Your email address will not be published. Required fields are marked *