Java 8中的Effective final与final的区别

13

我在主方法中执行以下内容:

int secrete = 42;
for (int i = 0; i < 5; i++) {
    Consumer<String> myprinter2 =
                    msg -> {
                        System.out.println("consuming " + msg + " ," + secrete);
                    };
     myprinter2.accept(myprinter2.toString());
}
以上代码的输出为:
consuming Main$$Lambda$1/1324119927@6d311334 ,42
consuming Main$$Lambda$1/1324119927@682a0b20 ,42
consuming Main$$Lambda$1/1324119927@3d075dc0 ,42
consuming Main$$Lambda$1/1324119927@214c265e ,42
consuming Main$$Lambda$1/1324119927@448139f0 ,42

如果我将 secrete 改为 final,那么输出结果将是:

consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42

secrete即使我没有声明为final,它也是有效的final变量,那么为什么当我没有将其声明为final时,每个lambda表达式都被认为是一个新的对象?


3
有趣的观察。我想区别在于明确声明为final的变量可以被视为编译时常量,而有效地被视为final的变量则不能。 - khelwood
1
同样有趣的是,如果你将 final int secrete 移到 for 循环中,你也会得到第二个输出。 - QBrute
1
@khelwood,你的理论对我来说没有意义。编译器才是决定什么是 effectively final 的人,不是吗?所以,如果编译器能够识别 effectively final,那么我认为编译器应该能够将 explicitly final 和 effectively final 视为相同。(虽然我不知道这段代码在做什么。) - Basil Bourque
3
@BasilBourque,“effectively final” 的意思是变量在初始化后不会再被改变,而不一定是编译时常量。在这种情况下它可以被定义为一个编译时常量,但也许编译器并没有寻找这个特定的情形。也许有人对此有更多的技术见解。 - khelwood
2
@BasilBourque,在处理final和effectively final时没有技术障碍,但正如这个答案中所解释的那样,规范不认为effectively final变量是编译时常量。因此,每当形式语义很重要时,编译器就不能像编译时常量一样对待它们。在这个特定的例子中,这并不重要,如此解释,这是一个实现细节。然而,优化这种情况将是一个特殊情况,使编译器更加复杂。 - Holger
@Holger 非常有趣,谢谢。我想要的结论是,如果你想要 final 行为,就要明确标记它。 - Basil Bourque
1个回答

2
"Effectively final"并非技术上必需,可以不使用。但是语言设计者设置了这个限制以避免混淆,因为如果变量不断变化,lambda将看到什么值,初始值还是最新值?其他具有lambda的语言没有这个限制,并且规范为此用例设置了期望值。
给定以下代码:
import java.util.function.Consumer;


class Main {  
  public static void main(String args[]) { 
    int i = 42;
    final int j = 41;
    for (int k = 0; k < 5; k++) {
      Consumer<String> x = msg -> System.out.printf("x=%s, i=%d%n", msg, i);
      Consumer<String> y = msg -> System.out.printf("y=%s, j=%d%n", msg, j);
      Consumer<String> z = msg -> System.out.printf("z=%s%n", msg);
      x.accept(x.toString());
      y.accept(y.toString());
      z.accept(z.toString());
    }
  } 
}

当我们使用命令 javap -c -v Main.class 检查生成的字节码时,我们可以看到:
11: invokedynamic #7,  0              // InvokeDynamic #0:accept:(I)Ljava/util/function/Consumer;
16: astore_3
17: invokedynamic #11,  0             // InvokeDynamic #1:accept:()Ljava/util/function/Consumer;
22: astore        4
24: invokedynamic #14,  0             // InvokeDynamic #2:accept:()Ljava/util/function/Consumer;

我们可以看到lambda表达式是如何被翻译的。相应的static方法表明第一个lambda表达式是一个捕获lambda表达式,在翻译后有一个整数第一个参数(#71),而其他lambda表达式则没有。
BootstrapMethods:
  0: #63 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #70 (Ljava/lang/Object;)V
      #71 REF_invokeStatic Main.lambda$main$0:(ILjava/lang/String;)V
      #74 (Ljava/lang/String;)V
  1: #63 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #70 (Ljava/lang/Object;)V
      #75 REF_invokeStatic Main.lambda$main$1:(Ljava/lang/String;)V
      #74 (Ljava/lang/String;)V
  2: #63 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #70 (Ljava/lang/Object;)V
      #78 REF_invokeStatic Main.lambda$main$2:(Ljava/lang/String;)V
      #74 (Ljava/lang/String;)V

所以,这只是lambda表达式的翻译方式。你可以在这篇文章中找到更多细节。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接