问题 将非最终对象传递给方法引用[重复]


这个问题在这里已有答案:


5176
2018-02-08 12:19


起源

Upvoted。这比简单的情况更有趣,因为“因为它是一个参考,所以”是一个合适的答案。 - Bathsheba
@khelwood: 类似 我会说。你可以写类似的东西 Supplier 在C ++中 将 输出“TWO”。 - Bathsheba
第二个例子不应该编译 - Andrew Tobilko


答案:


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

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

在代码中 x 和 s 被启动以引用2个对象然后 x 是为了引用另一个对象,但是 s 仍然指同一个对象。注意 :: 立即评估并生成对象。 x 可以独立于其他对象更改其引用 y

运用 x = "two" 只有 x 引用不同的对象。


5
2018-02-08 12:28





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

x = "two"; 

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


3
2018-02-08 12:24





仅通过lambda表达式传递最终或有效的最终变量(它起作用的原因)。使用方法引用,进行不同的评估,

15.13.3。方法参考的运行时评估 

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

所以变量不必是a 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
2018-02-08 12:31



这是否意味着toUpperCase方法只在“one”实例上调用一次? - Stephen L.
@StephenL。,该方法将被调用2次,但结果将仅被评估一次(当程序遇到方法引用表达式时)。我在答案中强调了这一点 - Andrew Tobilko


有趣的问题,所以我通过反编译器运行它 - 但答案支持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();
    }
}

2
2018-02-08 12:52





字符串是不可变的:

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

相当于:

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

0
2018-02-08 12:36





你创建了一个 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));

-1
2018-02-08 12:24



我创建了供应商,理由是对实例进行方法引用。 - Stephen L.
好吧,实例是一个带有文本“one”的String。因此,它永远不会改变,因为字符串是不可变的。您可能也写过:“one”:: toUpperCase。 - john16384