How to use Method References in Java?

As explained in previous posts , lamda expressions allow us to pass a functionality as a parameter.

We can dynamically choose function implementations and pass it to another method as an argument .

What if the functionality we want to pass already exists and we want to pass that ?

Use Method References!

Let’s consider the same use case used for lamda expressions.

We have a manager and an employee.

The manager needs to decide whether to grant promotion to the employee or not.

We will consider just checking the eligibility criteria of the employee for now.

Let’s create an interface CheckPromotionCriteria which has a single method check() which takes an employee as argument and validates if she is eligible for promotion.

Since it has only one method and it is an interface , it is a functional interface.

Now let’s look at the implementation.

Here is the client code:

public class Client {

	public static void main(String a[]) {
		

		Employee employee = new Employee();
		employee.setName("Kumar");
		employee.setExperienceInCompany(4);
		employee.setSkillSet("Java, Spring, AWS");
		employee.setTotalExperience(11);
		employee.setRating(4);

		CheckPromotionCriteria checkCriteria = new CheckPromotionCrteriaImpl();

		Manager manager = new Manager();

		// the regular way using a seperate class for valiation.
		manager.processPromotion(employee, checkCriteria);

}

We are creating an employee instance , an implementation for CheckPromotionCriteria interface , a manager instance .

And then we pass the employee and the interface reference to processPromotion() method.

This method checks the passed employee using the check() method present in CheckPromotionCriteria interface.

Here is the Manager code:

package methodreference;

public class Manager {

	String name;

	static boolean promotionToAll;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void processPromotion(Employee employee, CheckPromotionCriteria promotionCriteria) {

		if (promotionCriteria.check(employee)) {

			System.out.println("Promotion granted to :" + employee.getName());

		} else {

			System.out.println("Promotion not granted to :" + employee.getName());
		}

	}

}

As you see , the manager just uses the check() method of an implementation of CheckPromotionCriteria interface which is passed as a parameter to the method processPromotion.

Here is the CheckPromotionCriteriaImpl class:

package methodreference;

public class CheckPromotionCrteriaImpl implements CheckPromotionCriteria {

	@Override
	public boolean check(Employee e) {

		return e.getSkillSet().contains("Java") && e.getTotalExperience() > 10;
	}

}

In the method processPromotion() of Manager class, we just need a single method of CheckPromotionCriteriaImpl class. And we pass the entire object for that . What if we can just pass the method ? Method reference helps in that. Before that let’s see how to pass the functionality using lamdas.

Using lamdas , we can totally avoid creating an implementation class for CheckPromotionInterface .

We can remove the class CheckPromotionCriteriaImpl from our application.

We can pass a lamda expression instead with the validation criteria!

Since CheckPromotionInterface is a functional interface , it can be replaced by the lamda expression.

Here is the client code after introducing lamdas:

public class Client {

	public static void main(String a[]) {
		

		Employee employee = new Employee();
		employee.setName("Kumar");
		employee.setExperienceInCompany(4);
		employee.setSkillSet("Java, Spring, AWS");
		employee.setTotalExperience(11);
		employee.setRating(4);



		Manager manager = new Manager();

		// using lamda
		manager.processPromotion(employee, e -> e.getSkillSet().contains("Java") && e.getTotalExperience() > 10);

}

As you see the functionality is passed as a parameter. It is the same as present inside the check() method of CheckPromotionCriteriaImpl.

Both produce the same output.

Now let’s come to method references.

If you can pass lamda as a parameter to a function and the functionality represented by the lamda expression is already present inside another method , then we can refer to that method and pass it as a parameter instead of lamdas.

This is called Method Reference.

There are four different types of Method References.

Let’s see one by one .

Reference to a static method:

Let’s assume that the above functionality is present in Client code itself . Since we are invoking manager.processPromotion() from a static method (main() method) , we can only call other static methods inside Client class without creating an instance of it.

So let’s create a static method which checks if an employee is eligible for promotion:

public static boolean checkEmployeeEligibility(Employee e) {

		return e.getSkillSet().contains("Java") && e.getTotalExperience() > 10;
	}

To refer to a static method , we use the class name followed by :: symbol followed by the method name .

Here is how we can refer to the above static method :

Client::checkEmployeeEligibility

We do not pass the parameters while doing method reference. This is fetched indirectly from the other parameters passed along with it.

Here is our updated client code using static method reference:

public class Client {

	public static void main(String a[]) {
		

		Employee employee = new Employee();
		employee.setName("Kumar");
		employee.setExperienceInCompany(4);
		employee.setSkillSet("Java, Spring, AWS");
		employee.setTotalExperience(11);
		employee.setRating(4);

		
			// using method reference - static method + class reference
		manager.processPromotion(employee, Client::checkEmployeeEligibility);

}

	public static boolean checkEmployeeEligibility(Employee e) {

		return e.getSkillSet().contains("Java") && e.getTotalExperience() > 10;
	}



}

The parameter to the method checkEmployeeEligibility is fetched from the first parameter passed to processPromotion() method indirectly.

Now let’s look at the second type of method reference:

Reference to an instance type of a particular object:

Let’s assume the manager class itself has a method to check if an employee is an experienced Java programmer. Since right now the manager is using this criteria to grant promotion , lets refer to this method and pass it as an argument to processPromotion() method .

The lamda equivalent of this is:

manager.processPromotion(employee, e -> manager.isExperiencedJavaProgrammer(e));

Notice the input to the lamda expression (e which is a type of employee) and the method invoked inside the lamda. It is a method of another object ‘manager’.

This distinction is necessary to understand the next type of method reference. Since inside the lamda expression we are calling a method of a particular object (manager) and it is not the input parameter to the lamda expression , the method reference here is created using the manager instance:

manager.processPromotion(employee, manager::isExperiencedJavaProgrammer);

Here is the updated client code:

public class Client {

	public static void main(String a[]) {
		

		Employee employee = new Employee();
		employee.setName("Kumar");
		employee.setExperienceInCompany(4);
		employee.setSkillSet("Java, Spring, AWS");
		employee.setTotalExperience(11);
		employee.setRating(4);

			
		manager.processPromotion(employee, manager::isExperiencedJavaProgrammer);

	
		

	}
}

Now what if inside the lamda expression , we call a method of the same object passed as input to the lamda expression.

In this case we can use the third type of method reference. This took me a while to understand and most articles on the web are not explaining this straightforward.

Reference to an instance method of an arbitrary object of a particular type:

Let’s say the method to check if an employee is an experienced Java programmer is inside the employee class itself :

public boolean isExperiencedJavaProgrammer() {

		return this.skillSet.contains("Java") && this.totalExperience > 10;
	}

The lamda expression required to refer to the above method is :

manager.processPromotion(employee, e -> e.isExperiencedJavaProgrammer());

As you see , we are calling a method of an employee instance inside the lamda body. The same instance is also passed as input to the lamda expression.

if the same instance is used both as an input to the lamda expression and for invoking a method of that instance inside the lamda body , then you can use the third type of method reference.

“Reference to an instance method of an arbitrary object of a particular type”.

In the previous type (Reference to an instance method of a particular object), we called the method of a specific manager object ( e -> manager.isExperiencedJavaProgrammer(e)). The manager object is fixed , we had already created it outside the lamda expression . And the input to this lamda expression was another object (an employee object).

In this type though , the object whose instance method we invoke is arbitrary.

It is arbitrary because , whatever object is passed as an input to the lamda expression , we are going to invoke the method of that arbitrary object. The object is not specific.

The method reference can then be used like this :

manager.processPromotion(employee, Employee::isExperiencedJavaProgrammer);

As you see , we use the instance type (Employee) followed by :: symbol followed by the method name to refer to the method. We do not use the instance object here.

Here is the employee class with the new method:

package methodreference;

public class Employee {

	String name;

	private int experienceInCompany;

	private String skillSet;

	private int totalExperience;

	private int rating;

	public Manager manager;

	public Employee(String name, int experienceInCompany, String skillSet, int totalExperience, int rating) {

		this.name = name;
		this.setExperienceInCompany(experienceInCompany);
		this.setSkillSet(skillSet);
		this.setTotalExperience(totalExperience);
		this.rating = rating;
	}

	public Employee() {
		// TODO Auto-generated constructor stub
	}

	public boolean isExperiencedJavaProgrammer() {

		return this.skillSet.contains("Java") && this.totalExperience > 10;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getRating() {
		return rating;
	}

	public void setRating(int rating) {
		this.rating = rating;
	}

	public String getSkillSet() {
		return skillSet;
	}

	public void setSkillSet(String skillSet) {
		this.skillSet = skillSet;
	}

	public int getExperienceInCompany() {
		return experienceInCompany;
	}

	public void setExperienceInCompany(int experienceInCompany) {
		this.experienceInCompany = experienceInCompany;
	}

	public int getTotalExperience() {
		return totalExperience;
	}

	public void setTotalExperience(int totalExperience) {
		this.totalExperience = totalExperience;
	}

}

Here is the updated client code:

public class Client {

	public static void main(String a[]) {
		

		Employee employee = new Employee();
		employee.setName("Kumar");
		employee.setExperienceInCompany(4);
		employee.setSkillSet("Java, Spring, AWS");
		employee.setTotalExperience(11);
		employee.setRating(4);


		Manager manager = new Manager();

		// using method reference - non static method + class reference

		manager.processPromotion(employee, Employee::isExperiencedJavaProgrammer);
}

The last type of method reference is :

Reference to a constructor:

We can also pass the functionality of a constructor ( to create an object ) using method reference.

Let’ say whenever the manager recruits a new employee , he creates a new employee entry in the system with just three values – their name , skillset and total experience.

To create a new employee using these three values , let’s first create a constructor which takes these values:


	public Employee(String name, String skillSet, int totalExperience) {

		this.name = name;
		this.skillSet = skillSet;
		this.totalExperience = totalExperience;
	}

Now , lets not invoke this constructor directly inside our Manager class.

Let’s use method reference.

Remember , method references can be assigned only to functional interfaces.

So let’s create a functional interface which accepts these three values as input and returns an employee:

package methodreference;

public interface CreateEmployee {

	public Employee create(String name, String skillSet, int totalExperience);

}

It just has a single method which takes three inputs and return an employee.

Now let’s create a method inside Manager class which will create an employee instance using these three values:

public Employee createEmployee(String name, String skillSet, int totalExperience, CreateEmployee createEmployee) {

		return createEmployee.create(name, skillSet, totalExperience);
	}

Now , lets invoke this method using reference to the constructor of Employee class.

Here is the client code:

public class Client {

	public static void main(String a[]) {

		Manager manager = new Manager();

		
		Employee employeeNew = manager.createEmployee("Roy","Java, Angular", 8, Employee::new);
		
		System.out.println(employeeNew);

	}

As you see , the last parameter to the method createEmployee() is Employee::new which is a method reference to a constructor. This method reference automatically refers to constructor with three parameters that we had created and uses it to create a new Employee object.

We didn’t use the new operator inside Manager class yet we gave the power to create a new employee to it using method reference !

Here is the code (uses Java 14):

https://github.com/vijaysrj/javamethodreference


Posted

in

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading