1. Overview

Java 8 introduced the concept of method references. We often see them as similar to lambda expressions.

However, method references and lambda expressions are not exactly the same thing. In this article, we’ll show why they are different and what are the risks of using them in the wrong way.

2. Lambdas and Method References Syntax

To start with, let’s see a few examples of lambda expressions:

Runnable r1 = () -> "some string".toUpperCase();
Consumer<String> c1 = x -> x.toUpperCase();

And a few examples of method references:

Function<String, String> f1 = String::toUpperCase;
Runnable r2 = "some string"::toUpperCase;
Runnable r3 = String::new;

Those examples could make us think about method references as a shortened notation for lambdas.

But let’s take a look at the official Oracle documentation. We can find an interesting example there:

(test ? list.replaceAll(String::trim) : list) :: iterator

As we can see, the Java Language Specification allows us to have a different kind of expressions before double colon operator. The part before the :: is called the target reference.

Next, we’ll discuss the process of method reference evaluation.

3. Method Reference Evaluation

What is going to happen when we run the following code?

public static void main(String[] args) {
    Runnable runnable = (f("some") + f("string"))::toUpperCase;
}

private static String f(String string) {
    System.out.println(string);
    return string;
}

We’ve just created a Runnable object. Nothing more, nothing less. However, the output is:

some
string

It happened because the target reference is evaluated when the declaration is first spotted. Hence, we’ve lost the desired laziness. The target reference is also evaluated only once. So if we add this line to the above example:

runnable.run()

We will not see any output. What about the next case?

SomeWorker worker = null;
Runnable workLambda = () -> worker.work() // ok
Runnable workMethodReference = worker::work; // boom! NullPointerException

The explanation provided by the documentation mentioned before:

“A method invocation expression (§15.12) that invokes an instance method throws a NullPointerException if the target reference is null.”

The best way to prevent unexpected situations might be to never use variable access and complex expressions as target references.

A good idea might be to use method references only as a neat, short notation for its lambda equivalent. Having just a class name before :: operator guarantees safety.

4. Conclusion

In this article, we’ve learned about the evaluation process of method references.

We know the risks and the rules we should follow to not be suddenly surprised by the behavior of our application.