Saturday, January 2, 2021

Passing a function as parameter in Java

Passing function as parameter allows reusing of code that is in the parameters. In addition, this technique is also useful while implmenting different logic speciallywhen behavior parameterization is needed. This approach is also referred to passing a function or function pointer as an argument in some other programming languages. In this post, I am illustrating three ways to achieve this with a simple example. 

Conventional Method

First, I will use a conventional approach of creating interface, implementation and make use of the implemented interface for a method. In the second approach, I will use an anonymous class that implements interface. In the third approach I will use lambda which implements the needed function. These approaches go from verbose to less verbose. 

To get started I will create an interface with one method 'doJob'. The method takes JsonObject as parameter and does any job and returns string. The parameter can be null and so can be the return type. But only for illustration I am using the parameter and return type.


package Main;

import io.vertx.core.json.JsonObject;

@FunctionalInterface
public interface IFunctionPointer {
String doJob(JsonObject data);
}

The interface is declared with '@FunctionlInterface' to ensure that no multiple abstract method exists in the interface which will ensure that the lambda method can work with this interface. 

The "FunctionPointer" class implements the IFunctionPointer interface which simply adds 'doJob': true to data and returns the string representation of the input JsonObject.
  
package Main;

import io.vertx.core.json.JsonObject;

public class FunctionPointer implements IFunctionPointer {
@Override
public String doJob(JsonObject data) {
if(data == null){
data = new JsonObject();
}
data.put("Title", "Using function");
data.put("doJob", true);
return data.encodePrettily();
}
}


The doJob method is used by a consumer class that uses the "doJob" method. For example, I have a class 'UseFunctionPointer' which has a method 'useFunctionPointer' that makes use of the abstract method used in the interface.  


package Main;
import io.vertx.core.json.JsonObject;

public class UseFunctionPointer {

public static void main(String[] args) {
//Method 1: Use conventional
IFunctionPointer functionPointer = new FunctionPointer();
useFunctionPointer(new JsonObject(), functionPointer);
}

public static void useFunctionPointer(JsonObject data, IFunctionPointer p){
String result = p.doJob(data)
;
System.out.println(result);
}
}


When the UseFunctionPointer.main is executed, the doJob method in FunctionPointer class is called. The doJob method adds a title and doJob:true to the JsonObject and returns which essentially printed in the console as below:


{
  "Title" : "Using function",
  "doJob" : true
}


Now if the behavior of the doJob needs to be changed, it can simply be changed in the FunctionPointer class and rest of the code does not need to be changed. This is the very much conventional approach. 


Using Anonymous Class

Instead of using an implementer of the interface as done in the first approach, I could use anonymous class and put body for 'doJob' method at the time of class declaration. 


package Main;

import io.vertx.core.json.JsonObject;

public class UseFunctionPointer {

public static void main(String[] args) {
//Method 2: Use anonymous class
useFunctionPointer(new JsonObject(), new IFunctionPointer() {
public String doJob(JsonObject data) {
data = data == null ? new JsonObject() : data;
data.put("Title", "Using Anonymous class");
data.put("doJob", true);
return data.encodePrettily();
}
});

public static void useFunctionPointer(JsonObject data, IFunctionPointer p) {
String result = p.doJob(data);
System.out.println(result);
}
}


When the main method executes, the outcome will be as below: 


{
  "Title" : "Using Anonymous class",
  "doJob" : true
}


By instantiating an anonymous class from the interface IFunctionalPointer and implementing the method at the same time, it provided flexibility in the changing the behavior of the method 'doJob'. In the code above, the title of the data is changed to 'Using Anonymous class'. It must also be noted that the there is no need of a FunctionPointer class in the second approach.  


Using lambda

Using lambda does not even require declaring a class as was done in the previous approach. If there is correct method in the interface declared with @FunctionalInterface, an implementation can be provided as below:  



package Main;
import io.vertx.core.json.JsonObject;

public class UseFunctionPointer {

public static void main(String[] args) {

//Method 3: Use lambda
useFunctionPointer(new JsonObject(),
//data -> { //is also valid here
(JsonObject data) -> {
data.put("Title", "Using Lambda");
data.put("doJob", true);
return data.encodePrettily();
}
);
}

public static void useFunctionPointer(JsonObject data, IFunctionPointer p) {
String result = p.doJob(data);
System.out.println(result);
}
}


The lambda is much more concise and easier to read. The outcome of the above lambda-based approach is shown below. 


{
  "Title" : "Using Lambda",
  "doJob" : true
}

Conclusion

Passing function as a parameter provides an easy way to achieve behavioral parameterization where the logic is changed based on the functional constructs. In all the three approaches, useFunctionPointer method was called. This method calls 'doJob' method of the method, and lambda where the doJob method was passed as a parameter. Among all three methods, the verbosity is reduced from conventional to lambda-based approach while achieving the same functionality. Just to note, the lambda-based approach is only available for Java 8 and beyond. 

No comments:

Post a Comment