Javac缺少有效最终的优化

Jig*_*shi 5 java final compiler-optimization language-lawyer javacompiler

事实:

javac被编程以检测变量是否final或如果它可以被有效地 处理final.

证明:

此代码说明了这一点.

public static void finalCheck() {
        String str1 = "hello";
        Runnable r = () -> {
             str1 = "hello";
        };
}
Run Code Online (Sandbox Code Playgroud)

这无法编译,因为编译器能够检测到正在函数中重新分配String引用str1.

现在

情况1:

Javac final String通过避免创建StringBuilder和相关操作来对实例进行了很好的优化.

证明

这个java方法

  public static void finalCheck() {
    final String str1 = "hello";
    final String str2 = "world";
    String str3 = str1 + " " + str2;
    System.out.println(str3);
  }
Run Code Online (Sandbox Code Playgroud)

编译成

  public static void finalCheck();
    Code:
       0: ldc           #3                  // String hello world
       2: astore_2
       3: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: aload_2
       7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      10: return
Run Code Online (Sandbox Code Playgroud)

题:

但是现在我们有效地拥有它们 final

public static void finalCheck() {
    String str1 = "hello";
    String str2 = "world";
    String str3 = str1 + " " + str2;
    System.out.println(str3);
}
Run Code Online (Sandbox Code Playgroud)

它没有优化类似的方式,最终编译成

  public static void finalCheck();
    Code:
       0: ldc           #3                  // String hello
       2: astore_0
       3: ldc           #4                  // String world
       5: astore_1
       6: aload_0
       7: aload_1
       8: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      13: astore_2
      14: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      17: aload_2
      18: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      21: return
Run Code Online (Sandbox Code Playgroud)

JVM

$java -version
java version "10" 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10+46)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)
Run Code Online (Sandbox Code Playgroud)

编译器

$javac -version
javac 10
Run Code Online (Sandbox Code Playgroud)

问题:为什么不优化效果最终?

Hol*_*ger 5

引入有效最终概念并没有影响有关常量表达式和字符串连接的规则。

请参阅Java® 语言规范,第 15.18.1 节。字符串连接运算符 +

String对象是新创建的(第12.5 节),除非表达式是一个常量表达式(第15.28 节)。

参考部分,第12.5创建新类实例,消除任何疑问:

执行不属于常量表达式 ( §15.28 )的字符串连接运算符+( §15.18.1 ) 总是会创建一个新对象来表示结果。String

因此,尽管某些构造可能具有可预测的字符串结果,即使不是常量表达式,将它们替换为常量结果也会违反规范。只有常量表达式可以(事件必须)在编译时被它们的常量值替换。关于引用变量,§15.28 规定它们必须是根据 §4.12.4 的常量变量才能成为常量表达式:

常量变量final原始类型或类型的变量String,其与一个常量表达式(初始化§15.28)。

请注意对final常量变量的要求。

还有隐式 final变量的概念,它与有效 final不同:

三种变量都隐含声明final:接口(的场第9.3节),局部变量声明为的资源try-with-resources语句(§14.20.3),以及多的异常参数catch条款(§ 14.20)。uni-catch子句的异常参数永远不会被隐式声明final,但实际上可能是最终的。

因此,毫不奇怪,接口字段是隐式的final(它们也是隐式的static),因为它们总是如此,另外两种隐式final变量的情况永远不能是字符串,也不能是原始类型,因此永远不是常量。

有效地最终变量final仅在某些用例中被特殊对待(如变量)

  • 更自由地重新抛出捕获的异常(改进的类型检查)(自 Java?7)
  • 它们可能被 lambda 表达式和内部类引用(捕获)(自 Java?8)
  • 使用try-with-resource ( try(existingVariable) { … }(自 Java?9)引用它们

但除此之外,它们不会被视为final变量。

  • @Eugene,正如你在我的回答中看到的,Java 7 中引入了“有效最终”,其功能非常有限,并且每个版本都逐渐获得了新的用例。因此,如果它在未来版本中变得更多,请不要感到惊讶,即 [JEP303](http://openjdk.java.net/jeps/303) 建议新的“常量表达式”可以包含有效的最终变量(如果它们已初始化)与常量表达式。由于它与针对 Java 11 的 JEP 309 相关,因此它可能不会持续那么久。但“为什么我们今天没有得到它?”的答案 是“因为过去二十年没有改变”…… (3认同)
  • 您的意思是,违反规范还不足以成为不这样做的理由吗? (2认同)
  • 顺便说一句,你说你使用了 javac 10 但字节码看起来像 Java 9 之前的版本,这让我很恼火。你使用了“-target”或“--release”选项吗? (2认同)