Java Custom Functional Interface

By Arvind Rai, April 06, 2019
This page will walk through Java custom functional interface example. Java provides @FunctionalInterface annotation to create functional interface. @FunctionalInterface is available since Java 8. A functional interface has exactly one abstract method. Functional interface can be initialized using lambda expressions, method references, or constructor references. A functional interface can have default methods. Functional interfaces can also be created by inheriting another functional interface. Java provides built-in functional interfaces such as Supplier, Consumer, Predicate etc.
Here on this page we will create our custom functional interfaces using @FunctionalInterface annotation. We will create functional interfaces with generics, default methods and by inheritance in our example. We will also provide examples to initialize functional interfaces using lambda expressions, method references, or constructor references. Now let us discuss to create custom functional interfaces step by step.

@FunctionalInterface

1. @FunctionalInterface annotation is used to create a functional interface.
2. A functional interface has exactly one abstract method.
3. Default methods of interface are not counted as abstract as they have implementation.
4. If functional interface declares an abstract method overriding one of the public methods of Java Object class, that will also not be counted.
5. The instances of functional interface can be created by using lambda expressions, method references, or constructor references.

Create Functional Interface

To create our functional interface, we need to create an interface annotated with @FunctionalInterface and exactly one abstract method. An abstract method within an interface is followed by a semicolon, but no braces.
Calculator.java
package com.concretepage;

@FunctionalInterface
public interface Calculator {
   long calculate(long num1, long num2);
} 
Here we have created Calculator interface with abstract method calculate. The interface Calculator is annotated with @FunctionalInterface and in this way we have created a functional interface i.e. Calculator. We can instantiate a functional interface using lambda expressions, method references, or constructor references.

Instantiate Functional Interface using Lambda Expression

Here we will instantiate a functional interface using lambda expression. Find the lambda expression syntax.
(Argument  part)  -> Body part 
Now we will instantiate our functional interface Calculator as following.
Calculator calc = (n1, n2) -> n1 + n2; 
In the above lambda expression, number of arguments is two because the abstract method calculate has been defined with two arguments. To get the result, we will call the method of our functional interface.
System.out.println(calc.calculate(30, 50)); 
The output will be 80.

Passing Functional Interface as Method Argument using Lambda Expression:

Now create a method in a class whose argument will be our functional interface type as following.
public long process(Calculator calc) {
    return calc.calculate(this.firstNum, this.secondNum);
} 
The class will look like as following.
MyNumber.java
package com.concretepage;
public class MyNumber {
    private long firstNum;
    private long secondNum;
    public MyNumber(long firstNum, long secondNum) {
	   this.firstNum = firstNum;
	   this.secondNum = secondNum;
    }
    public long process(Calculator calc) {
       return calc.calculate(this.firstNum, this.secondNum);
    }
    //setters getters
} 
We can directly pass lambda expression as an argument or the instance of functional interface to process method in the above class. Suppose we have a list of MyNumber as following.
List<MyNumber> list = new ArrayList<>();
list.add(new MyNumber(100, 40));
list.add(new MyNumber(300, 60));
list.add(new MyNumber(60, 20)); 
We can run our functional interface in following ways.
Example-1:
Here we are creating object of our functional interface and then passing it as argument for summation.
Calculator calc = (n1, n2) -> n1 + n2;
for(MyNumber myNumber: list) {
   System.out.println(myNumber.process(calc));
} 
Find the output.
140
360
80 
Example-2:
Here we are directly passing lambda expression as an argument for multiplication.
for(MyNumber myNumber: list) {
   System.out.println(myNumber.process((n1, n2) -> n1 * n2));
} 
Find the output.
4000
18000
1200 
Example-3:
Here we are performing division.
for(MyNumber myNumber: list) {
   System.out.println(myNumber.process((n1, n2) -> n1 / n2));
} 
Find the output.
2
5
3 

Instantiate Functional Interface using Method Reference

Method reference invokes method using (::) sign. Suppose we have a class MyNumber and a static method add then we can call it using class name.
MyNumber::add 
If add is not a static method then we can call this method using instance of the class. Suppose myNumber is the instance of MyNumber class and add is non-static method, then we call it using instance as given below.
myNumber::add 

To create instance of functional interface using method reference we need to create a method with same method declarations as abstract method. The method in our functional interface Calculator is as following.
long calculate(long num1, long num2); 
Now we have created two static methods add and multiply in our utility class with same declarations as abstract method of functional interface. Find the utility class.
Utility.java
package com.concretepage;
public class Utility {
    public static long add(long num1, long num2) {
    	return num1 + num2;
    }
    public static long multiply(long num1, long num2) {
    	return num1 * num2;
    }  
} 
Now instantiate functional interface using static method of Utility class as following.
Calculator calc = Utility::add;
System.out.println(calc.calculate(30, 50)); 
Output will be 80.

Passing Functional Interface as Method Argument using Method Reference:

Now let us use our MyNumber class with method reference. We have already created MyNumber class and a list of its objects above. Now find the use of method reference. First we are using utility add method.
for(MyNumber myNumber: list) {
   Calculator calc = Utility::add;
   System.out.println(myNumber.process(calc));
} 
We can also pass reference method directly to the method as given below.
System.out.println(myNumber.process(Utility::add)); 
Find the output.
140
360
80 
Now we are using utility multiply method.
for(MyNumber myNumber: list) {
   System.out.println(myNumber.process(Utility::multiply));
} 
Find the output.
4000
18000
1200 
Now let us understand how the above codes are working. To understand it, look into the definition of process method of MyNumber class.
public long process(Calculator calc) {
   return calc.calculate(this.firstNum, this.secondNum);
} 
When we call process(Utility::add) and process(Utility::multiply) then our functional interface Calculator is instantiated with the definition of add and multiply method of Utility class respectively. When calculate method is called with given arguments then we get results.

Instantiate Functional Interface using Constructor Reference

Here we will instantiate a functional interface using constructor reference. We need to use new keyword for constructor reference. Find the constructor reference for Utility class.
Utility::new 
As we know that constructor has no return type. So we will create a functional interface with abstract method that has no return type but with same number of arguments and type as constructor. Find a functional interface.
TaskHandler.java
package com.concretepage;

@FunctionalInterface
public interface TaskHandler {
  void get(String tname);
} 
We have created constructor in Utility class as given below.
Utility.java
public class Utility {
  public Utility(String taskName) {
    System.out.println(taskName);
  }
  ------
} 
Now let us instantiate our functional interface and run it.
TaskHandler taskHandler = Utility::new;
taskHandler.get("Task 1"); 
The output will be "Task 1".

Functional Interface with Default Methods

We can create default methods in our functional interface. Find the functional interface Worship.
Worship.java
package com.concretepage;

import java.util.Objects;

@FunctionalInterface
public interface Worship {
  void chant(String name);
  
  default Worship again(Worship w) {
	  return (name) -> {
		Objects.requireNonNull(w);  
		chant(name);
		w.chant(name);
	  };
  }
} 
We have created a default method named as again. The type of parameter is Worship itself. The return type is also Worship. Now we have to define our default method. As the default method is returning Worship, we need to return a function which defines its abstract method i.e. chant. Now look into the definition of default method.
default Worship again(Worship w) {
  return (name) -> {
	Objects.requireNonNull(w);  
	chant(name);
	w.chant(name);
  };
} 
Objects.requireNonNull checks that the specified object reference is not null. In the above code the method chant(name) is the method of caller Worship instance. w.chant(name) is of the argument Worship instance. If we call again method many times, the result will be chained. Now let us run the example.
Worship worship = (name) -> System.out.println(name);

worship.again(worship).again(worship).chant("Ram"); 
Find the output.
Ram
Ram
Ram 
Now let us instantiate Worship with some changes and then run it.
Worship worship = (name) -> {
   System.out.println(name);
   System.out.println(name);
}; 

worship.again(worship).again(worship).chant("Ram"); 
Find the output.
Ram
Ram
Ram
Ram
Ram
Ram 

Functional Interface with Generic and Default Methods

We will create some functional interfaces with generics here. We will also add default methods to use those functional interfaces.
Functional interface 1:
DataCombiner.java
package com.concretepage;

@FunctionalInterface
public interface DataCombiner<T> {
   String combine(T t);
} 
Functional interface 2:
ExtraInfoProvider.java
package com.concretepage;

@FunctionalInterface
public interface ExtraInfoProvider<R> {
   R provideMore(R r);
} 
Functional interface 3:
Now find the InfoProvider functional interface that will use DataCombiner and ExtraInfoProvider in its default methods.
InfoProvider.java
package com.concretepage;

import java.util.Objects;

@FunctionalInterface
public interface InfoProvider<T, R> {
  R provide(T t);
  
  default InfoProvider<T, R> addMore(ExtraInfoProvider<R> more) {
	  return (T t) -> {
		 Objects.requireNonNull(more); 
		 R r = provide(t);
		 return more.provideMore(r);
	  };
  }
  
  default DataCombiner<T> addCombiner(DataCombiner<R> combiner) {
	  return (T t) -> {
		  Objects.requireNonNull(combiner);
		  return combiner.combine(provide(t));
	  };
  }
} 
In the above code we have created an abstract method provide and two defaults methods addMore and addCombiner. The default method addMore is returning InfoProvider, so within addMore we will return a function definition of provide abstract method of InfoProvider functional interface. Objects.requireNonNull checks that the specified object reference is not null.
In the addCombiner method, we are returning DataCombiner, so within this method we will return function definition of combine abstract method of DataCombiner functional interface.

Suppose we have two classes Employee and Project as following.
Project.java
public class Project {
    private String pname;
    private String teamLead;
    private String location;
    public Project(String pname, String teamLead) {
	    this.pname = pname;
	    this.teamLead = teamLead;
    }
    //getters and setters
} 
Employee.java
public class Employee {
    private int id;
    private String name;
    public Employee(int id, String name) {
	    this.id = id;
	    this.name = name;
    }
    //getters and setters
} 
Now we will initialize our functional interfaces using lambda expressions. Initialize DataCombiner, ExtraInfoProvider, InfoProvider and run it.
DataCombiner<Project> dataCombiner = (Project p) -> {
	if(p.getLocation() == null) {
		return p.getPname()+" : " + p.getTeamLead();
	} else {
		return p.getPname()+" : " + p.getTeamLead() + " : " + p.getLocation();
	}
};

InfoProvider<Employee, Project> infoProvider = (Employee emp) -> {
	if(emp.getId() > 100) {
		return new Project("ABCD", emp.getName());
	} else {
		return new Project("PQRS", emp.getName());
	}
};

InfoProvider<Employee, Project> infoProvider = (Employee emp) -> {
	if(emp.getId() > 100) {
		return new Project("ABCD", emp.getName());
	} else {
		return new Project("PQRS", emp.getName());
	}
};

String s = infoProvider.addMore(extraInfoProvider)
		.addCombiner(dataCombiner).combine(new Employee(50, "Mahesh"));

System.out.println(s); 
Find the output.
PQRS : Mahesh : Noida 

Functional Interface Inheritance

We can create functional interface by inheriting existing one. Suppose we have a functional interface as following.
DataCombiner.java
package com.concretepage;

@FunctionalInterface
public interface DataCombiner<T> {
   String combine(T t);
} 
Now we will create DataReceiver functional inheritance by extending DataCombiner and add a default method.
DataReceiver.java
package com.concretepage;

import java.util.Objects;

@FunctionalInterface
public interface DataReceiver<T> extends DataCombiner<T> {
	
	default void receive(TaskHandler handler, T t) {
		Objects.requireNonNull(handler);
		handler.get(combine(t));
	}
} 
In the default method receive, we are passing TaskHandler functional interface. Find the TaskHandler.
TaskHandler.java
package com.concretepage;

@FunctionalInterface
public interface TaskHandler {
  void get(String tname);
} 
Instantiate DataReceiver and TaskHandler and then run it.
DataReceiver<Employee> dataReceiver = (Employee emp) -> emp.getId() + "-" + emp.getName();

TaskHandler tskHandler = (res) -> System.out.println(res); 

dataReceiver.receive(tskHandler, new Employee(101, "Krishna")); 
Find the output.
101-Krishna 

Reference

Java doc: @FunctionalInterface

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI







©2024 concretepage.com | Privacy Policy | Contact Us