将非最终对象传递给方法引用

时间:2017-02-08 12:19:40

标签: java lambda java-8 method-reference expression-evaluation

s.get()第二次返回“ONE”的解释是什么?

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

更新

将其与:

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

它会抛出编译错误。

6 个答案:

答案 0 :(得分:5)

在java变量中,引用对象通常称为references。在上面的代码中,您有两个引用,xs

字符串是不可变的,任何更改都代表另一个Object。创建后,您无法修改String对象的任何状态。

在代码中,xs都被启动以引用2个对象,然后x引用另一个对象,但s仍然引用同一个对象。请注意,会立即评估::并生成对象。 x可以将其对其他对象的引用更改为y

使用x = "two"只会使x引用其他对象。

答案 1 :(得分:3)

String是一个不可改变的类,你正在做

x = "two"; 

保持对象 s “完整”与之前的值“ONE”

答案 2 :(得分:3)

只有lambda表达式(the reasons why it works so)才能传递最终或有效的最终变量。使用方法引用,进行不同的评估,

  

15.13.3. Run-Time Evaluation of Method References

     

当方法引用表达式::分隔符之前具有表达式(而不是类型)时,将立即计算该子表达式。存储评估结果,直到调用相应功能接口类型的方法为止;此时,结果将用作调用的目标引用。这意味着仅当程序遇到方法引用表达式时才会计算::分隔符之前的表达式,并且不会在函数接口类型上的后续调用中重新评估

所以变量不一定是final

实际上,无论一个类是否是不可变的。相反,如果方法引用的左侧部分是表达式,则很重要。

我想举一个简短的例子让你明白:

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. 已创建供应商实例,尚未评估结果(带有类型的方法参考)。
  2. 已计算供应商及其结果(带有new A()表达式的方法参考)。
  3. 对于每次supplier1.get()来电,都会重新评估。
  4. 将返回第2步的结果。

答案 3 :(得分:2)

有趣的问题所以我通过反编译器运行它 - 但答案支持Andrew Tobilko回答

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());
    }
}

因此方法引用获取x的第一个实例的副本,这就是为什么它输出“ONE”两次,并且没有创建静态lambda,只调用toUpper

我还运行了第二个创建lambda的例子(我错过了不编译的部分 -

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();
    }
}

答案 4 :(得分:0)

字符串是不可变的:

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

相当于:

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

答案 5 :(得分:-1)

您创建了一个Supplier,它只提供值,在这种情况下每次都是相同的值,因为此处的x值仅在创建lambda时转换一次。

你想要的是一个Function,它接受​​一个参数并返回一个结果。

试试这个:

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));