Java 8 – Method and Constructor References

In this article, we will discuss one of the alternatives for Lambda Expression i.e.; Method references and Constructor references

Lambda expression basically provides some functionality (or logic) in anonymous method where it can be invoked using Functional Interface’s abstract method. This Functional Interface can be pre-defined or our own created custom interface.

1. Method references using double colon operator (::)

  • Method reference is basically an alternative to Lambda expression and for code re-usability
  • If suppose we have already method implementation for our functionality (or logic) then instead of writing/coding our own implementation for lambda expression we can simply call already implemented using method references
  • We can call either instance method or static method, depending upon where it is implemented
  • Important rule being number of input arguments must match

We will examine with example using Lambda Expression and later we will rewrite same example using Method References

1.1 Lambda expression example

  • Here, we have Functional interface called Demo which has Single Abstract Method “sum()” taking 2 primitive-type int
  • Test class has “main()” method which contains lambda expression for Functional Interface’s abstract method for summing/adding 2 numbers and simultaneously printing to console
package net.bench.resources.method.reference.example;

@FunctionalInterface
interface Demo {
	// SAM
	void sum(int a, int b);
}

public class AddNumbers {

	public static void main(String[] args) {

		// lambda expression to add 2 numbers
		Demo i = (a, b) -> System.out.println(a+b);
		i.sum(300, 200);
	}
}

Output:

500

1.2 Method Reference to a static method

  • Here, we are reusing same Functional interface called Demo which has Single Abstract Method “sum()” taking 2 primitive-type int
  • Test class has 2 static methods namely “add()” and “main()”
  • add() method implementation is exactly same as that of lambda expression we have seen in the earlier example 1.1
  • This example is simple that it prints to console after summing/adding 2 numbers. But, think when we have large set of statements, there method references comes handy instead of rewriting whole lambda
  • Note: method reference is useful whenever we have implementation ready with us in method. Otherwise, lambda expression is best for concise and precise code
package net.bench.resources.method.reference.example;

@FunctionalInterface
interface Demo {
	// SAM
	void sum(int a, int b);
}

public class AddNumbers {

	// static method which has same implementation
	public static void add(int x, int y) {

		//  similar to above lambda expression
		System.out.println(x+y);
	}

	public static void main(String[] args) {

		// method reference using :: operator
		Demo i = AddNumbers::add;
		i.sum(25, 75);
	}
}

Output:

100

How to call static method for method reference ?

  • For static methods, use <ClassName>::<StaticMethodName>
  • Only thing to be taken care is, number of input arguments of Single Abstract Method of Functional interface must match with referencing static method
  • In above example, Demo interface has a method called sum with 2 input arguments and referencing method add also takes same number of input arguments
  • In case of any difference in number of input arguments, then compile-time error will be thrown stating “The type AddNumbers does not define add(int, int) that is applicable here

1.3 Method Reference to an instance method of a particular object

  • Here, we are reusing same Functional interface called Demo which has Single Abstract Method “sum()” taking 2 primitive-type int
  • Test class has 1 instance method named “add()” and another static “main()” method
  • add() method implementation is exactly same as that of lambda expression we have seen in the first example 1.1
  • Note: method reference is preferred only when there is already method implementation available otherwise lambda expression still holds good
package net.bench.resources.method.reference.example;

@FunctionalInterface
interface Demo {
	// SAM
	void sum(int a, int b);
}

public class AddNumbers {

	// instance method which has same implementation
	public void add(int x, int y) {

		//  similar to above lambda expression
		System.out.println(x+y);
	}

	public static void main(String[] args) {

		// method reference using :: operator - instance method
		AddNumbers an = new AddNumbers();
		Demo i = an::add;
		i.sum(625, 175);
	}
}

Output:

800

How to call instance method for method reference ?

  • To invoke instance method, we have to create object (instance) of a class using new operator like AddNumbers an = new AddNumbers();
  • And then use <ObjectReference>::<InstanceMethodName> to call instance methods
  • Again, only thing that has to be taken care is, number of input arguments of Single Abstract Method of Functional interface must match with referencing instance method
  • In above example, Demo interface has a method called sum() with 2 input arguments and referencing instance method add() also accepts same number of input arguments
  • In case of any difference in number of input arguments, then compile-time error will be thrown stating “The type AddNumbers does not define add(int, int) that is applicable here

1.4 Method Reference to an instance method of an arbitrary object of a particular type

  • This is very much similar to last example
  • The only difference between the two is that, we don’t need to create object instead instance method is referenced from an arbitrary object
  • Syntax to refer method of an arbitrary object is <className>::<instanceMethodName>
  • For example, String class has many instance methods like compareTo(), compareToIgnoreCase(), length(), toUpperCase(), toLowerCase(), etc.
  • So, we can refer these instance methods of String class like String::toUpperCase or String::length or String::compareToIgnoreCase to convert String into upper case, finding length of String or sorting ignoring case-sensitive correspondingly
  • Below example depicts sorting of String values from List using compareTo() method without ignoring case-sensitive
package net.bench.resources.method.reference.example;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SortingStringUsingArbitraryObject {

	public static void main(String[] args) {

		// list of cricketer names
		List<String> names = Arrays.asList(
				"Sachin",
				"Dhoni",
				"Yuvraj",
				"Zaheer",
				"Sehwag",
				"Virat",
				"Gambhir"
				);

		System.out.println("1. Sorting using Lambda expression : \n");

		// before Java 8
		List<String> sortingUsingLambda = names
				.stream()
				.sorted((str1, str2) -> str1.compareTo(str2))
				.collect(Collectors.toList());

		System.out.println(sortingUsingLambda);



		System.out.println("\n\n2. Sorting using"
				+ " Method reference to an arbitrary object : \n");

		// sorting using method reference in Java 8
		List<String> sortingUsingMethodReference = names
				.stream() // get stream
				.sorted(String::compareTo) // Method reference to arbitrary object
				.collect(Collectors.toList()); // collecting to List

		System.out.println(sortingUsingMethodReference);
	}
}

Output:

1. Sorting using Lambda expression : 

[Dhoni, Gambhir, Sachin, Sehwag, Virat, Yuvraj, Zaheer]


2. Sorting using Method reference to an arbitrary object : 

[Dhoni, Gambhir, Sachin, Sehwag, Virat, Yuvraj, Zaheer]

2. Constructor references using double colon operator (::)

  • If single abstract method of Functional Interface returns Object, then we can use Constructor References instead of writing/coding lambda expression
  • Syntax : <ClassName>::new
  • Again, important rule is number of input arguments must match

We will examine with example using Lambda Expression and later we will rewrite same example using Constructor References

2.1 Lambda expression example

  • Here, we have 3 things
  • Student class – we want return Student object whenever lambda expression is invoked
  • DemoExample – this Functional Interface has one abstract method called getStudentObject(); of return-type Student
  • main class – this is where we are writing lambda expression and invoking functional interface’s method to return Student object
package net.bench.resources.method.reference.example;

class Student {
	// public no-args constructor
	Student() {
		System.out.println("Student default constructor");	
	}
}

@FunctionalInterface
interface DemoExample {

	// single abstract method with return-type Student
	Student getStudentObject();
}

// test class
public class ConstructorReferenceExample {

	public static void main(String[] args) {

		// lambda expression to create Student
		DemoExample d = () -> new Student();

		// invoking lambda to create Student object
		d.getStudentObject();
	}
}

Output:

Student default constructor

2.2 Constructor Reference example – no arg constructor

  • This example is same as that of earlier one in 2.1 with only difference is that, it states how can we write constructor reference instead of lambda expression
  • Student class and DemoExample Functional Interface is same
  • But in main class, we have replaced lambda expression with constructor reference as DemoExample d = Student::new;
  • And invoking Functional Interface’s abstract method using above constructor references i.e.; d.getStudentObject();
package net.bench.resources.method.reference.example;

class Student {
	// public no-args constructor
	Student() {
		System.out.println("Student default constructor");	
	}
}

@FunctionalInterface
interface DemoExample {

	// single abstract method with return-type Student
	Student getStudentObject();
}

// test class
public class ConstructorReferenceExample {

	public static void main(String[] args) {

		// constructor reference to create Student
		DemoExample d = Student::new;

		// invoking FI method to create Student object
		d.getStudentObject();
	}
}

Output:

Student default constructor

2.3 Constructor Reference example with input arguments – parameterized arg constructor

  • This example is same as that of earlier one in 2.2 with only difference is that, it is parameterized with 4 input arguments
  • Also, Student class overrides toString() method to pretty-print Student values
package net.bench.resources.method.reference.example;

class Student {

	// member variables
	String name;
	int roll;
	int age;
	int marks;

	// public parameterized constructor
	public Student(String name, int roll, int age, int marks) {
		super();
		this.name = name;
		this.roll = roll;
		this.age = age;
		this.marks = marks;
	}

	// toString method
	@Override
	public String toString() {
		return "Student [name=" + name 
				+ ", roll=" + roll 
				+ ", age=" + age 
				+ ", marks=" + marks + "]";
	}
}

@FunctionalInterface
interface DemoExample {

	// single abstract method with return-type Student
	Student getStudentObject(String name, int roll, int age, int marks);
}

// test class
public class ConstructorReferenceExample2 {

	public static void main(String[] args) {

		// constructor reference to create Student
		DemoExample d = Student::new;

		// invoking FI method to create Student object
		Student s = d.getStudentObject("Surya", 4, 37, 75);

		// print to console
		System.out.println(s);
	}
}

Output:

Student [name=Surya, roll=4, age=37, marks=75]

References:

Happy Coding !!
Happy Learning !!

Java 8 - Introduction to Stream API
Java 8 - BinaryOperator and its primitive Functional Interface