Search code examples
javalambdaequalsmethod-referencefunctional-interface

Why Functional interface initialize different when use lambda in factory-method and method reference (singleton / prototype)?


I have two factory-methods which produce "consumers" use different approaches 👉🏻 lambda and method references:

@SuppressWarnings("Convert2MethodRef")
public Consumer<String> lambdaPrintStringConsumer(){
    return x -> System.out.println(x);
}

public Consumer<String> methodRefPrintStringConsumer(){
    return System.out::println;
}

I found that in first case (lambdaPrintStringConsumer()), method return reference to the same object

@Test
public void shouldSameFromFactoryMethod_lambda() {
    Consumer<String> consumerA = lambdaPrintStringConsumer();
    Consumer<String> consumerB = lambdaPrintStringConsumer();
    
    Assert.assertSame(consumerA, consumerB);//consumerA == consumerB --> true
}

but in the second (methodRefPrintStringConsumer()), objects is different

@Test
public void shouldNotSameFromFactoryMethod_methodRef() {
    Consumer<String> consumerA = methodRefPrintStringConsumer();
    Consumer<String> consumerB = methodRefPrintStringConsumer();

    Assert.assertNotSame(consumerA, consumerB);//consumerA == consumerB --> false
}

direct approach return the same result as shouldNotSameFromFactoryMethod_methodRef():

@SuppressWarnings("Convert2MethodRef")
@Test
public void shouldNotSameFromLambda() {
    Consumer<String> consumerA = s -> System.out.println(s);
    Consumer<String> consumerB = s -> System.out.println(s);

    Assert.assertNotSame(consumerA, consumerB);//consumerA == consumerB --> false
}

, next I tested factory-method with method reference to other static method

public class FunctionalInterfaceTest {

    public static Consumer<String> methodRefFromStaticMethodStringConsumer() {
        return FunctionalInterfaceTest::print;
    }

    public static void print(String string) {
        System.out.println(string);
    }

    ...

}

and get the same result as in the first test (lambdaPrintStringConsumer):

@Test
public void shouldSameFromFactoryMethod_methodRef() {
    Consumer<String> consumerA = methodRefFromStaticMethodStringConsumer();
    Consumer<String> consumerB = methodRefFromStaticMethodStringConsumer();

    Assert.assertSame(consumerA, consumerB );//consumerA == consumerB --> true
}

What is the trick

In Tests 👉🏻 jdk-11.0.1 and jdk-13.0.1.


Solution

  • Are the following expressions equivalent?

    x -> System.out.println(x)
    
    System.out::println
    

    No. If you call System.setOut, the former will pick up the new PrintStream; the latter will not.

    So, in this case, the lambda method does not require access to variables from the enclosing lexical scope, whereas this method reference expression does. This allow the former to be shared but the latter cannot.

    The exact details may or may not be specified - I can't be bothered to look.