Java Method Reference Evaluation

Along with lambda expressions, Java SE 8 introduced method references as a shorthand notation. These are mostly used to reference static methods (e.g. Double::toString) or constructors (e.g. String[]::new), and these uses are straightforward. However, method references to instance methods can yield results that differ from lambda expressions in surprising ways. This is because the invocation target of a method reference – the part before the :: – is evaluated when its declaration is first encountered, whereas lambda expressions are only evaluated when actually called.

The following program calls instance methods in various ways, using both method references and lambda expressions, to demonstrate this different behavior. Below we’ll go through the output case by case to see what’s happening.

class MethodRefTest {

    public static void main(String[] args) {
        System.out.println("\nConstructor in method reference");
        final Runnable newRef = new Counter()::show;
        System.out.println("Running...");
        newRef.run(); newRef.run();

        System.out.println("\nConstructor in lambda expression");
        final Runnable newLambda = () -> new Counter().show();
        System.out.println("Running...");
        newLambda.run(); newLambda.run();

        System.out.println("\nFactory in method reference");
        final Runnable createRef = Counter.create()::show;
        System.out.println("Running...");
        createRef.run(); createRef.run();

        System.out.println("\nFactory in lambda expression");
        final Runnable createLambda = () -> Counter.create().show();
        System.out.println("Running...");
        createLambda.run(); createLambda.run();

        System.out.println("\nVariable in method reference");
        obj = new Counter(); // NPE if after method reference declaration! 
        final Runnable varRef = obj::show;
        System.out.println("Running...");
        varRef.run(); obj = new Counter(); varRef.run();

        System.out.println("\nVariable in lambda expression");
        obj = null; // no NPE, lambda expression declaration not evaluated
        final Runnable varLambda = () -> obj.show();
        System.out.println("Running...");
        obj = new Counter(); varLambda.run();
        obj = new Counter(); varLambda.run();

        System.out.println("\nGetter in method reference");
        final Runnable getRef = get()::show;
        System.out.println("Running...");
        getRef.run(); obj = new Counter(); getRef.run();

        System.out.println("\nGetter in lambda expression");
        final Runnable getLambda = () -> get().show();
        System.out.println("Running...");
        getLambda.run(); obj = new Counter(); getLambda.run();
    }

    static Counter obj;

    static Counter get() {
        System.out.print("get: ");
        return obj;
    }

    static class Counter {
        static int count;
        final int myCount;

        Counter() {
            myCount = count++;
            System.out.println(String.format("new Counter(%d)", myCount));
        }

        static Counter create() {
            System.out.print("create: ");
            return new Counter();
        }

        void show() {
            System.out.println(String.format("Counter(%d).show()", myCount));
        }
    }
}

Constructor

The first block calls methods on a directly created new instance of the Counter class. This class keeps track of the number of instances created during the current run and also identifies each instance by its creation index. Here is the output:

Constructor in method reference
new Counter(0)
Running...
Counter(0).show()
Counter(0).show()

Constructor in lambda expression
Running...
new Counter(1)
Counter(1).show()
new Counter(2)
Counter(2).show()

Method reference and lambda expression are both called twice, correctly resulting in two show outputs. However, the constructor call specified in each case is only evaluated once for the method reference, upon its declaration. The created object is then reused. The lambda expression does nothing upon declaration and instead calls the constructor each time it is run.

Factory Method

The second block is equivalent to the first but uses factory methods rather than constructors to obtain new Counter objects. The results are the same as before, showing that the different evaluation order of method expressions is not related to directly using new in the invocation target expression.

Factory in method reference
create: new Counter(3)
Running...
Counter(3).show()
Counter(3).show()

Factory in lambda expression
Running...
create: new Counter(4)
Counter(4).show()
create: new Counter(5)
Counter(5).show()

Variable Access

The third block tests variable access, here using a static field because lambda expressions do not accept mutable local variables.

Variable in method reference
new Counter(6)
Running...
Counter(6).show()
new Counter(7)
Counter(6).show()

Variable in lambda expression
Running...
new Counter(8)
Counter(8).show()
new Counter(9)
Counter(9).show()

The method reference’s immediate evaluation of its invocation target has two consequences. First, the field initialization must appear before the declaration, or a NullPointerException occurs. This is not the case for the lambda expression where we can reset the field to null before its declaration, just as long as the field is valid during invocation.

Second, as the target reference is stored upon immediate evaluation, it does not change when the field is subsequently assigned a newly created Counter, unlike the lambda expression which fetches the current field value every time it is run.

Getter Method

The last block is equivalent to the variable example but uses a getter method to read the current field value. Once again the backing field is changed between invocations.

Getter in method reference
get: Running...
Counter(9).show()
new Counter(10)
Counter(9).show()

Getter in lambda expression
Running...
get: Counter(10).show()
new Counter(11)
get: Counter(11).show()

As shown by the get: appearing once before Running... for the method expression, the getter is called only once upon declaration. Accordingly, the returned field value is used for both show calls. The lambda expression calls the getter twice, obtaining the updated field value on the second call.

Analysis

This behavior is described in the end note to §15.13.3 “Run-time Evaluation of Method References” in the Java SE 8 Language Specification:

The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.

I first encountered this difference to lambda expressions when I tried using method references to create dialog instances within a MenuItem handler, as in the constructor test case. I was surprised to discover that each invocation would open what was clearly the same instance, with control contents from the previous invocation still intact. Replacing the method references with lambda expressions yielded the expected behavior of creating a new dialog instance on each invocation.

The immediate target evaluation of method references is likely undesirable in most cases where the target can be expected to change between invocations. Consider using method references only for static methods and constructors (X::new), or with instance references that are known to remain unchanged for all invocations. If there is any chance that the target reference might need dynamic re-evaluation you will have to use a lambda expression.

Note: The original version of this post had a silly error in its test program that caused me to assume some additional strange behavior of method references that does not actually exist. Thanks to jwolfe and Tom Anderson for quickly pointing out the error!

One thought on “Java Method Reference Evaluation

  1. Manish

    Very use full to understands method reference in context on instance method..Thanks buddy to publish this article..

    Reply

Leave a Reply