Java 8 Constructor Refs (In All Their Glory)

[Note: my last post announced my new book, Modern Java Recipes, is now available from O’Reilly publishers in Early Release form. As a sample, I included a discussion of the Predicate interface, one of the new functional interfaces in the the java.util.function package. In this post, I highlight constructor references, which are discussed in another recipe in the book.]

Problem

You want to instantiate an object using a method reference as part of a stream pipeline.

Solution

Use the new keyword as part of a method reference.

Discussion

When people talk about the new syntax added to Java 8, they mention lambda expressions, method references, and streams. For example, say you had a list of people and you wanted to convert it to a list of names. One way to do so would be:

List<String> names = people.stream()
    .map(person -> person.getName()) // lambda expression
    .collect(Collectors.toList());

In other words, take a list of Person instances, turn it into a stream, map each one to a String by invoking the getName() method on it, and collect them back into a List.

That works, but most developers would use a method reference instead:

List<String> names = people.stream()
    .map(Person::getName)           // method reference
    .collect(Collectors.toList());

The method reference is slightly shorter, and makes clear that the only thing being done to each person is transforming it using the getName method. Lambda expressions can be far more complicated and versatile. Method references are simple.

What if you want to go the other way? What if you have a list of strings and you want to create a list of Person references from it? In that case you can use a method reference again, but this time using the keyword new. That’s a constructor reference, which I would like to illustrate here.

First, here is the Person class, which is just about the simplest Plain Old Java Object (POJO) imaginable. All it does is wrap a simple string attribute called name.

public class Person {
    private String name;

    public Person() {}   // default constructor

    public Person(String name) {
        this.name = name;
    }

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

    // ... equals, hashCode, toString methods ...

Given a list of string names, I can map them to Person instances using the one-argument constructor.

List<String> names =  
    Arrays.asList("Grace Hopper", "Barbara Liskov", "Ada Lovelace",
        "Karen Spärck Jones");

List<Person> people =
    names.stream()
         .map(name -> new Person(name))  // lambda expression
         .collect(Collectors.toList());

Now instead of using the lambda expression that invokes the one-argument constructor directly, I can use a constructor reference instead.

names.stream()
     .map(Person::new)     // Constructor reference
     .collect(Collectors.toList());

Like all lambda expression or method references, context is everything. The map method is invoked on a stream of strings, so the Person::new reference is invoked on each string in the stream. The compiler recognizes that the Person class has a constructor that takes a single string, so it calls it. The default constructor is ignored.

Copy Constructors

To make things more interesting, I’ll add two additional constructors: a “copy constructor” that takes a Person argument, and one that takes a variable argument list of strings.

public Person(Person p) {   // copy constructor
    this.name = p.name;
}

public Person(String... names) {  // varargs constructor
    this.name = Arrays.stream(names)
                      .collect(Collectors.joining(" "));
}

The copy constructor makes a new Person from an existing Person instance. Say I defined a person, then used that person in a stream without mapping it, and then converted back into a collection. Would I still have the same person?

Person before = new Person("Grace Hopper");

List<Person> people = Stream.of(before)
        .collect(Collectors.toList());
Person after = people.get(0);

assertTrue(before == after);  // exact same object

before.setName("Grace Murray Hopper");  // Change name using 'before'
assertEquals("Grace Murray Hopper", after.getName()); // Same in 'after'

The point is, if I have a reference to Admiral Hopper before the stream operations and I didn’t map her to another object, I still have the same reference afterwards.

Using a copy constructor I can break that connection.

people = Stream.of(before)
        .map(Person::new)          // use copy constructor
        .collect(Collectors.toList());
after = people.get(0);
assertFalse(before == after);      // different objects
assertEquals(before, after);       // but equivalent

before.setName("Rear Admiral Dr. Grace Murray Hopper"); // Change using 'before'
assertFalse(before.equals(after)); // No longer the same in 'after'

This time, when invoking the map method, the context is a stream of Person instances. Therefore the Person::new syntax invokes the constructor that takes a Person and returns a new, but equivalent, instance. I’ve broken the connection between the before reference and the after reference.

(Btw, I mean no disrespect by treating Admiral Hopper as an object. I have no doubt she could still kick my a**, and she passed away in 1992.)

Varargs Constructors

The varargs constructor is invoked by the client by passing zero or more string arguments separated by commas. Inside the constructor, the names variable is treated like String[], a string array. The static stream method on the Arrays class is used to convert that into a stream, which is then turned into a single string by calling the collect method, whose argument comes from the convenient joining(String delimiter) method in the Collectors class.

How does that get invoked? Java includes a split method on String that takes a delimiter and returns a String array:

String[] split(String delimiter)

Since the variable argument list is equivalent to an array, I can use that method to invoke the varargs constructor.

names.stream()                     // Stream<String>
    .map(name -> name.split(" "))  // Stream<String[]>
    .map(Person::new)              // Stream<Person> using String... ctor
    .collect(Collectors.toList());

This time I map the strings to string arrays before invoking the constructor. Note that this is one of those times where I can’t use a method reference, because there’s no way using a method reference to supply the delimiter argument. I have to use the lambda expression instead.

Since the context after the first map is now a stream of string arrays, the Person::new constructor reference now uses the varargs constructor. If I add a print statement to that constructor:

System.out.println("Varargs ctor, names=" + Arrays.asList(names));

I can then see it in action:

Varargs ctor, names=[Grace, Hopper]
Varargs ctor, names=[Barbara, Liskov]
Varargs ctor, names=[Ada, Lovelace]
Varargs ctor, names=[Karen, Spärck, Jones]

Arrays

With constructor references, not only can you create instances, you can even create arrays. Say instead of returning a List, I wanted to return an array of person, Person[]. The Stream class has a method called, naturally enough, toArray.

<A> A[] toArray(IntFunction<A[]> generator)

This method uses A to represent the generic type of the array returned containing the elements of the stream, which is created using the provided generator function. The cool part is that a constructor reference can be used for that, too.

names.stream()
     .map(Person::new)         // Person constructor ref
     .toArray(Person[]::new);  // Person[] constructor ref

The returned value is a Person[] with all the stream elements now included as Person references.

Constructor references are just method references by another name, using the word new to invoke a constructor. Which constructor is determined by the context, as usual. This technique gives a lot of flexibility when processing streams.

About Ken Kousen
I teach software development training courses. I specialize in all areas of Java and XML, from EJB3 to web services to open source projects like Spring, Hibernate, Groovy, and Grails. Find me on Google+ I am the author of "Making Java Groovy", a Java / Groovy integration book published by Manning in the Fall of 2013, and "Gradle Recipes for Android", published by O'Reilly in 2015.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: