Javac缺少有效最终

时间:2018-04-17 02:25:51

标签: java language-lawyer compiler-optimization final javacompiler

事实:

javac被编程为检测变量是final还是可以被视为有效 final

证明:

此代码说明了这一点。

public static void finalCheck() {
        String str1 = "hello";
        Runnable r = () -> {
             str1 = "hello";
        };
}

这无法编译,因为编译器能够检测到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);
  }

编译到

  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

问题:

但现在我们将它们作为有效地 final

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

它没有优化相似的方式并最终编译成

  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

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)

编译器

$javac -version
javac 10

问题:为什么没有针对有效的决赛进行优化?

1 个答案:

答案 0 :(得分:4)

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

请参阅Java® Language Specification, §15.18.1. String Concatenation Operator +

  

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

引用的部分§12.5. Creation of New Class Instances消除了任何疑问:

  

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

因此,虽然某些构造可能具有可预测的字符串结果,但即使不是常量表达式,用常量结果替换它们也会违反规范。只有常量表达式可以(事件必须)在编译时被它们的常量值替换。关于引用变量,§15.28声明它们必须是constant variables according to §4.12.4为常量表达式:

  

常量变量是基本类型或类型final的{​​{1}}变量,使用常量表达式(§15.28)进行初始化。

请注意String为常量变量的要求。

还有隐式最终变量的概念,这与有效最终不同:

  

隐式声明了三种变量final:接口的一个字段(§9.3),一个声明为final资源的局部变量--with-resources语句({ {3}}),以及多个try子句(§14.20.3)的异常参数。 uni - catch子句的异常参数永远不会隐式声明catch,但可能实际上是最终的。

因此,没有太多令人惊讶的是,接口字段隐式final(它们也隐含final),因为它们一直是,而其他两个隐式static变量的情况永远不会是字符串,也不是原始类型,因此永远不是常数。

有效的最终变量仅在某些用例中被特别处理(如final变量)

  • Rethrowing以更多的自由(改进的类型检查)捕获异常(自Java7起)
  • 它们可以由lambda表达式和内部类(自Java8起)
  • 引用(捕获)
  • 使用final - with-resource(try(自Java9起)
  • 引用它们

但除此之外,它们不会被视为try(existingVariable) { … }变量。