Passing code as a parameter
-
Last Updated: July 21, 2023
-
By: javahandson
-
Series
In this article, we will learn the use of Passing code as a parameter in Java 8. We will also learn what verbosity issues we face and we will move away from it to the anonymous class and then to the lambda expressions
We all know the fact that user requirements always keep on changing.
One day an employer has to filter the employees whose designation is Manager. The next day employer says ‘We have to filter all the employees whose salary is greater than 50000’. After a few days again he comes and say ‘It would be really nice if we can filter the employees whose designation is Manager and salary greater than 50000’. It is very difficult to cope with these changing requirements.
We have to write the below pieces of code to filter the employees based on different requirements.
Filter the employees with the designation of Manager
public static List < Employee > filterByDesignation(List < Employee > employees) { List < Employee > result = new ArrayList < > (); for (Employee employee: employees) { if (employee.getDesignation().equals("Manager")) { result.add(employee); } } return result; }
Filter the employees with salaries greater than 50000
public static List < Employee > filterBySalary(List < Employee > employees) { List < Employee > result = new ArrayList < > (); for (Employee employee: employees) { if (employee.getSalary() > 50000) { result.add(employee); } } return result; }
Filter the employees with designation as Manager and salary greater than 50000
public static List < Employee > filterByDesignationAndSalary(List < Employee > employees) { List < Employee > result = new ArrayList < > (); for (Employee employee: employees) { if (employee.getDesignation().equals("Manager") && employee.getSalary() > 5000) { result.add(employee); } } return result; }
Every time we have to update the codebase to change the filtering logic or we have to write a new filter method. This piece of code looks so bad. We are creating a new list every time and also we are iterating through the list every time. Here we are duplicating the steps multiple times. What if in the future employer comes up with new complex requirements? we have to do some logic changes and also repeat statements in the new filter method. Isn’t it bad? Obviously Yes!
As Java developers, we have to minimize the development efforts as well as we have to write a codebase that is easily maintainable and can be easily extended. But with the above approach, it is very difficult to achieve that.
Passing code as a parameter can help us to handle the frequent requirement changes.
Passing code as a parameter means creating a block of code and passing it to other parts of the program without executing it. We will pass this block of code via a parameter to another method. This other method will execute the block of code later when required.
So let us try to achieve this in our previous example. We will use the concept of abstraction to make things easy and readable.
We have to filter employees based on some conditions. If the employee matches the condition or if the filter condition is true then only we are adding it to the result list.
We will create an interface with a method that will return a boolean.
package com.javahandson; public interface EmployeePredicate { boolean test(Employee employee); }
Now we can declare multiple implementations of EmployeePredicate for multiple selection criteria.
Predicate implementation to filter the employees based on designation
package com.javahandson; public class EmployeeByDesignationPredicate implements EmployeePredicate { @Override public boolean test(Employee employee) { if (employee.getDesignation().equals("Manager")) { return true; } return false; } }
Predicate implementation to filter the employees based on salary
package com.javahandson; public class EmployeeBySalaryPredicate implements EmployeePredicate { @Override public boolean test(Employee employee) { if (employee.getSalary() > 50000) { return true; } return false; } }
Now we can pass this predicate to the filter method
public static List < Employee > filterEmployees(List < Employee > employees, EmployeePredicate predicate) { List < Employee > result = new ArrayList < > (); for (Employee employee: employees) { if (predicate.test(employee)) { result.add(employee); } } return result; }
To call this filter method we need to pass the predicate and employee list. This filtering logic will always be the same just the predicate will change. In the future, if some new requirement comes up then we can create a new predicate implementation and pass it to this filter method.
The above code is much more flexible and easy to read than the one which we created in our first attempt. Now if the employer wants to filter the employees based on designation and salary then he can create a new predicate implementation and pass it to the filter method
package com.javahandson; public class EmployeeByDesignationAndSalaryPredicate implements EmployeePredicate { @Override public boolean test(Employee employee) { if (employee.getDesignation().equals("Manager") && employee.getSalary() > 5000) { return true; } return false; } }
In the above code, we have to declare a new implementation class every time when the filtering logic changes. We again have to instantiate this predicate class and then pass it to the filter method like below
public static void main(String[] args) { Employee employee1 = new Employee("Amar", "Engineer", 40000); Employee employee2 = new Employee("Iqbal", "Manager", 60000); Employee employee3 = new Employee("Suchit", "Manager", 55000); Employee employee4 = new Employee("Kartik", "Clerk", 35000); List < Employee > list = new ArrayList < > (); list.add(employee1); list.add(employee2); list.add(employee3); list.add(employee4); List < Employee > result = filterEmployees(list, new EmployeeByDesignationAndSalaryPredicate()); System.out.println(result); } Output: [Employee[name = Iqbal, designation = Manager, salary = 60000.0], Employee[name = Suchit, designation = Manager, salary = 55000.0]]
Creating implementation classes is an unnecessary overhead. Instead, we can create anonymous classes and pass them to the filter method. Anonymous classes do not have any name and also it allows to declare and instantiate classes at the same time.
List < Employee > result = filterEmployees(list, new EmployeePredicate() { @Override public boolean test(Employee employee) { if (employee.getDesignation().equals("Manager") && employee.getSalary() > 5000) { return true; } return false; } });
Here we don’t have to implement the predicate interface we can directly provide the implementation using an anonymous class.
But does it look good? No right!
These anonymous classes are very bulky and it consumes a lot of space.
A lambda expression is a block of code that takes in parameters, executes the logic and returns a value. The lambda expression is just like a method. We can pass the lambda expression in the argument of another method. So it is like passing one method to another method.
Syntax of lambda expression : (parameter1, parameter2) -> { code block }
Filter the employee based on designation
List<Employee> result = filterEmployees(list, (Employee employee) -> employee.getDesignation().equals("Engineer")); System.out.println(result); Output : [Employee [name=Amar, designation=Engineer, salary=40000.0]]
In the above example, we have passed a lambda expression to the filter method.
The lambda expression looks so concise and up to the mark. We don’t have to write any new classes nor do we have to create any anonymous class. It is very readable and understandable.
So this is all about passing code as a parameter. This is the most important concept in Java 8 and we will be using this concept everywhere. We will be passing the code as a lambda expression in java 8 programming.