Uses of Constructor (Method) Reference

A constructor reference, a kind of method reference, can be used as a simple factory method or a conversion function.

Tutorial | Jan 29, 2020 | nextptr 

Background

The method references, along with lambda expressions, is a functional programming feature added in Java 8. A method reference is a handle to an existing method, which, like a lambda expression, can be invoked through a functional interface. Consider a functional interface, Work, and a class Worker:

@FunctionalInterface
interface Work {
 void perform();
}

//A worker class
class Worker {
 void doWork() { /*..code..*/ }
}

The method doWork can be invoked through a method reference as follows:

//Create a Worker instance 
Worker worker = new Worker();
//assign method ref. to functional interface
Work work = worker::doWork; //note '::' 

//invoke method through method ref.
work.perform(); //invokes worker.doWork

You might ask, what is the need for method references if we have lambda expressions? The motivation behind method references is simple: It is better to use an existing method reference directly for readability than wrapping a call to that existing method in a lambda expression. For instance, compare a method invocation through method reference and lambda expression below:

//Create a Worker instance 
Worker worker = new Worker(); 

/*Following 2 techniques are equivalent.
  Clearly #2 is better for readability.*/

//1. Call worker::doWork through a lambda
Work lwork = () -> worker.doWork(); 
lwork.perform(); 

//2. Call worker::doWork through method ref.
Work rwork = worker::doWork;
rwork.perform();  

Method references are of three types: static, instance, and constructor. As their names suggest, static method references are references to static methods, and instance method references are references to instance methods. And finally, constructor references are references to constructors. However, constructor references have a special syntax; we use <ClassName>::new to get a constructor reference, as shown below:

interface Factory<T> { T make(); }

//assign constructor ref. to functional interface
Factory<Worker> f = Worker::new; 

Worker w = f.make(); //Creates a Worker

Uses of Constructor Reference

Constructor references are quite useful as handles to factory methods and conversion functions. Let's look at a few use cases below.

1. Generic Factory Method
Consider a class hierarchy of document parsers, as shown below. The XMLParser and JSONParser can parse the documents in XML and JSON formats respectively:

interface Parser {
 //...interface methods
}

class XMLParser implements Parser {
 XMLParser(String docString) {
  //..expensive code 
 }
 //.. fields
}

class JSONParser implements Parser {
 JSONParser(String docString) {
  //..expensive code 
 }
 //.. fields
}

These parsers are heavyweight objects and require a considerable amount of resources, and should only be constructed when required. Accordingly, the UseParser method, instead of taking a parser instance, takes a generic a factory to construct a parser only if required:

@FunctionalInterface
interface ParserCreator<P> {
 P create(String docString);
}

//The method that creates/uses a parser
static <P> void 
UseParser(ParserCreator<P> pc, String docString) {
 //Some code
 //Create parser if required
 P p = pc.create(docString);
 //...
}

The constructor reference of a parser would be an ideal choice for a factory method argument to the UseParser, as shown below:

/*Pass constructor reference as 
  parser creator (factory method)*/
UseParser(XMLParser::new, 
          "<XML Document String>");

2. Factory Method with Stream API
Here we have another example of constructor reference as a factory method. The Stream API is quite useful in applying a chain of operations on collections. In the following code, we are filtering the even numbers from a List and collecting them in another List:

List<Integer> ints = Arrays.asList(2,3,2,6,5,7,8);
//Collect even integers
List<Integer> evens = ints.stream()
                          .filter((n) -> n%2 == 0)   
                          .collect(Collectors.toList()); 

However, if we want to collect the unique even-numbers in a specific collection (say TreeSet), then we have to use toCollection method of Collectors. The Collectors.toCollection method requires a functional-interface (Supplier) parameter as a factory method to construct a collection. We can pass a constructor reference of a collection as a Supplier, as follows:

//Collect unique even numbers in a TreeSet. 
TreeSet<Integer> uevens = ints.stream()
                              .filter((n) -> n%2 == 0)
                              .collect(Collectors.toCollection(TreeSet::new));

3. Conversion Function
A constructor that takes only one parameter can be thought of as a conversion function. For instance, the Person(String) constructor below, essentially, converts a String to a Person object:

class Person {
 Person(String name) {
  //...
 }
 //...
}

We can use the Person::new as a mapper argument to Stream's map operation to create Person objects from names:

//List of names
List<String> names = Arrays.asList( "John", "Jane" );    
//Map names to Person objects and get a List<Person>
List<Person> persons = names.stream()
                            .map(Person::new)
                            .collect(Collectors.toList());

Further Reading

Method References: Oracle Java Docs

Java Streams: Oracle Java Docs

2015 nextptr