【问题标题】:Passing non final objects to method references [duplicate]将非最终对象传递给方法引用[重复]
【发布时间】:2017-02-08 12:19:39
【问题描述】:

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

会抛出编译错误。

【问题讨论】:

  • 赞成。这比“因为它是一个参考,init”是一个合适的答案的更简单的情况更有趣。
  • @khelwood:类似我会说。您可以在 C++ 中编写类似于 Supplier 的内容,输出“TWO”。
  • 第二个例子不应该编译

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


【解决方案1】:

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

字符串是不可变的,所做的任何更改都代表另一个对象。一旦创建,您就不能修改 String 对象的任何状态。

在代码中,xs 都被初始化为引用 2 个对象,然后 x 被设置为引用另一个对象,但 s 仍然引用同一个对象。请注意,立即评估 :: 并分配结果对象。 x 可以将其引用更改为独立于y 的另一个对象

使用x = "two" 只会使x 引用不同的对象。

【讨论】:

    【解决方案2】:

    String 是一个不可变的类,而你正在这样做

    x = "two"; 
    

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

    【讨论】:

      【解决方案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 步的结果。

      【讨论】:

      • 这是否意味着 toUpperCase 方法只在“一个”实例上调用一次?
      • @StephenL.,该方法将被调用2次,但结果只会被评估一次(当程序遇到方法引用表达式时)。我在答案中突出显示了它
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        字符串是不可变的:

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

        相当于:

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

          • 我创建 Supplier 的原因是有一个对实例的方法引用。
          • 嗯,实例是一个带有文本“one”的字符串。因此它永远不会改变,因为字符串是不可变的。你不妨写:"one"::toUpperCase。
          猜你喜欢
          • 2014-05-01
          • 2021-09-20
          • 2016-07-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多