Java 8 Series: Constructor and Method References

This series’ recent post showed that lambda expressions can be assigned to variables whose type is a functional interface, e.g. like this: Runnable runForrest = () -> Forrest.run();. Today we’ll learn how to initialize such functional interface-typed variables with method and constructor references. Picking up the previous example that means that we’ll see how to directly reference the Forrest.run() method.

Call ya reference!

At first please take a look at the below class that we’ll refer to in the subsequent examples:

There’s nothing special about it apart from the fact that it bears two constructors (a default and a parameterized one) as well as different kinds of methods (namely instance and static methods). Now how can we reference each of them in order to initialize variables with them, hand them round and finally execute them?

Method References

Let’s start with the static method createMessage() which we assign to a variable of type Function<P,R>1:

We see that the syntax for method references is pretty straightforward: RECEIVER::METHODNAME. The RECEIVER is somewhat holding the method (identified by METHODNAME) we want to reference. Hence in the above example the receiver is the Message class and the method name is createMessage (don’t forget to omit the parentheses).

Exactly the same syntax applies if we want to capture the reference of a method that is hold by an instance of a particular type:

Here we reference the toString() method of some, not yet existent Message instance. As we can see the ‘missing’ instance gets later on passed to the apply() method. We can also obtain references of methods belonging to concrete instances, though:

This time the receiver is an instance whose getText() method we assign to a variable of type Supplier<R>2.

Finally, capturing method references of supertypes is possible as well:

Constructor References

A constructor references’ receiver is always the name of the class that is defining the constructor. But what about the method name? Is it also the class name? Nope. It’s the well-known new keyword, as we see below:

We initialize a variable of type Supplier<Message> with the Message class’ default constructor. But stop! The Message class holds two constructors. How did the compiler know that we mean the default constructor and not the parameterized one? That’s the point where the concept type inference should be coined. With type inference the compiler infers the required or missing information from the context. In the above example the context of the constructor reference contains the target variable messageFactory. It’s of type Supplier<Message> and therefore does not allow parameters. Thus the compiler assumes the reference of the default constructor.

Analogous, if we want to obtain the reference of the parameterized Message(String text) constructor, the target type should allow one parameter of type String, as we see below:

Our last example deals with an array constructor reference. If we would like to create an array of ten Message objects we would write new Message[10];. Consequently the target variable type should allow one parameter of type Integer (defining the array’s length) and a Message[] return type:

Please note that the receiver of array constructor references must be written with square brackets.

Conclusion

Thanks to constructor and method references it’s possible to write more compact and readable Java code, especially in connection with a fluent API as the one of the enhanced Java Collections Framework, e.g. like that: brood.stream().filter(Brood::eagerToLearn).forEach(broodlab::register);. In the next part we’ll dive deeper into that just brought up feature known as streams.

Other Posts in this Series

References

Footnotes

  1. Function<P,R> represents a method that receives one parameter of type P and returns a result of type R.
  2. Supplier<R> represents a method that receives none parameter and returns a result of type R.