11

What is the explanation that s.get() returns "ONE" the second time as well?

String x = "one";
Supplier<String> s = x::toUpperCase;
System.out.println("s.get() = " + s.get());
x = "two";
System.out.println("s.get() = " + s.get());

Update:

Compare it to:

String x = "one";
Supplier<String> s = () -> x.toUpperCase();
System.out.println("s.get() = " + s.get());
x = "two";
System.out.println("s.get() = " + s.get());

It will throw a compilation error.

Stephen L.
  • 509
  • 2
  • 14

6 Answers6

5

In java variables referring an objects are usually called as references. In the above code you have two references , x and s.

Strings are immutable and any change done, represents another Object. Once created you can not modify any state of the String object.

In the code both x and s are initilized to refer 2 objects and then x is made to refer another object, but s still refers to same object. note that :: is evaluated immediately and resulting object is assiged. x can change its reference to another object independent of y

Using x = "two" only makes x to refer to a different object.

nits.kk
  • 5,204
  • 4
  • 33
  • 55
3

String is an inmutable class and you are doing

x = "two"; 

keeping the object s "intact" with the previous value "ONE"

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
3

Passing final or effectively final variables is required only by lambda expressions (the reasons why it works so). With method references, which are evaluated differently,

15.13.3. Run-Time Evaluation of Method References

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.

so it is not necessary that a variable has to be a final.

Actually, it has no matter whether a class is immutable. Rather, it is important if the left part of a method reference is an expression or not.

I would like to show a short example to make you understand:

class A {
    public static void main(String[] args) {

        Supplier<A> supplier1 = A::new; // (1)
        Supplier<A> supplier2 = new A()::self; // (2) 

        A r1 = supplier1.get(); // (3)
        A r2 = supplier2.get(); // (4)
    }

    private A self() { return this; }

}
  1. A supplier instance has been created, the result has not been evaluated yet (a method reference with a type).
  2. A supplier and its result have been calculated (a method reference with the new A() expression).
  3. For each supplier1.get() call, it will be re-evaluated.
  4. The result from step 2 will be returned.
Community
  • 1
  • 1
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • Does it mean that toUpperCase methods is called only once on "one" instance? – Stephen L. Feb 08 '17 at 12:34
  • 1
    @StephenL., the method will be called 2 times, but the result will be evaluated only once (when the program encounters the method reference expression). I highlighted it in the answer – Andrew Tobilko Feb 08 '17 at 12:45
2

Interesting question so I ran it through a decompiler - but the answer supports Andrew Tobilko answer

java -jar cfr_0_119.jar LambdaTest --decodelambdas false

/*
 * Decompiled with CFR 0_119.
 */
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.function.Supplier;

public class LambdaTest {
    public static void main(String[] args) {
        String x = "one";
        Supplier<String> s = (Supplier<String>)LambdaMetafactory.metafactory(null, null, null, ()Ljava/lang/Object;, toUpperCase(), ()Ljava/lang/String;)((String)x);
        System.out.println("s.get() = " + s.get());
        x = "two";
        System.out.println("s.get() = " + s.get());
    }
}

So the method reference is getting a copy of the first instance of x which is why it outputs "ONE" twice, and a static lambda isnt created, only a call to toUpper

I also ran the second example which does create a lambda(I missed out the part that doesnt compile -

java -jar cfr_0_119.jar LambdaTest --decodelambdas false
/*
 * Decompiled with CFR 0_119.
 */
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.function.Supplier;

public class LambdaTest {
    public static void main(String[] args) {
        String y = "one";
        Supplier<String> sy = (Supplier<String>)LambdaMetafactory.metafactory(null, null, null, ()Ljava/lang/Object;, lambda$0(java.lang.String ), ()Ljava/lang/String;)((String)y);
        System.out.println("sy.get() = " + sy.get());
    }

    private static /* synthetic */ String lambda$0(String string) {
        return string.toUpperCase();
    }
}
farrellmr
  • 1,815
  • 2
  • 15
  • 26
0

Strings are immutable:

String x = "one";
Supplier<String> s = x::toUpperCase;

is equivalent to:

String x = "one";
Supplier<String> s = "one"::toUpperCase;
john16384
  • 7,800
  • 2
  • 30
  • 44
-1

You created a Supplier, which just supplies values, and in this case the same value every time, because the value of x here is only converted once when the lambda is created.

What you want is a Function, something that takes an argument and returns a result.

Try this:

String x = "one";
Function<String, String> s = String::toUpperCase;
System.out.println("s.apply(x) = " + s.apply(x));
x = "two";
System.out.println("s.apply(x) = " + s.apply(x));
john16384
  • 7,800
  • 2
  • 30
  • 44
  • I created Supplier for a reason to have a method reference to the instance. – Stephen L. Feb 08 '17 at 12:32
  • Well, the instance is a String with text "one". It will therefore never change, as Strings are immutable. You might as well have written: "one"::toUpperCase. – john16384 Feb 08 '17 at 12:35