Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different behavior between lambda expression and method reference by instantiation

As I know lambda expression can be replaced by method reference without any issues. My IDEs say the same, but the following example shows the opposite. The method reference clearly returns the same object, where as lambda expression returns new objects each time.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Instance {

    int member;

    Instance set(int value){
        this.member = value;
        return this;
    }

    @Override
    public String toString() {
        return member + "";
    }

    public static void main(String[] args) {

        Stream<Integer> stream1 = Stream.of(1, 2, 3, 4);
        Stream<Integer> stream2 = Stream.of(1, 2, 3, 4);

        List<Instance> collect1 = stream1.map(i -> new Instance().set(i)).collect(Collectors.toList());
        List<Instance> collect2 = stream2.map(new Instance()::set).collect(Collectors.toList());

        System.out.println(collect1);
        System.out.println(collect2);
    }
}

Here is my output:

[1, 2, 3, 4]
[4, 4, 4, 4]
like image 373
st.ebberr Avatar asked Aug 30 '18 12:08

st.ebberr


People also ask

Are there any differences between lambda expressions and method references?

Lambda expression is an anonymous method (method without a name) that has used to provide the inline implementation of a method defined by the functional interface while a method reference is similar to a lambda expression that refers a method without executing it.

Is method reference better than lambda?

Lambdas are clearer The method reference syntax could stand in for either one. It hides what your code is actually doing.

What is the difference between lambda expression and inner class instance in terms of this reference?

A lambda expression is a short form for writing an anonymous class. By using a lambda expression, we can declare methods without any name. Whereas, Anonymous class is an inner class without a name, which means that we can declare and instantiate class at the same time.

What is lambda and method reference used for?

You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it's often clearer to refer to the existing method by name.


2 Answers

The timing of method reference expression evaluation differs from which of lambda expressions.
With a method reference that has an expression (rather than a type) preceding the :: the subexpression is evaluated immediately and the result of evaluation is stored and reused then.
So here :

new Instance()::set

new Instance() is evaluated a single time.

From 15.12.4. Run-Time Evaluation of Method Invocation (emphasis is mine) :

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.

like image 160
davidxxx Avatar answered Oct 09 '22 08:10

davidxxx


Your lambda expression is calling new Instance() each time it's executed. This explains why the result of its toString() is different for each element.

The method reference retains the instance off which it is referenced, such that it's similar to:

Instance instance = new Instance();
List<Instance> collect2 = stream2.map(instance::set).collect(Collectors.toList());

The result of using the method reference in this case is that the same instance is used to call set, collected at the end. The member value displayed is the one last set.


As an experiment, make these changes and observe that the instance is changing in the case of the lambda expression:

/* a random string assigned per instance */
private String uid = UUID.randomUUID().toString();

Instance set(int value) {
    this.member = value;
    System.out.println("uid: " + uid); //print the ID
    return this;
}
like image 39
ernest_k Avatar answered Oct 09 '22 09:10

ernest_k