7

事实:

javac被编程来检测变量是否有效final或是否可以有效 final处理。

证明:

这段代码说明了这一点。

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

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

现在

情况一:

final StringJavac通过避免创建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

虚拟机

$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

问题:为什么它不针对有效的 final 进行优化?

4

1 回答 1

6

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

请参阅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 节)、声明为-with-resources 语句的资源的局部变量(第14.20.3节)和多子句的异常参数(第14.20 )。uni- 子句的异常参数永远不会被隐式声明,但可能是有效的最终参数。trycatchcatchfinal

因此,毫不奇怪,接口字段是隐式的final(它们也是隐式的static),就像它们一直以来的那样,并且其他两种隐式final变量永远不会是字符串或原始类型,因此永远不是常量。

final仅在某些用例中有效地对最终变量进行特殊处理(如变量)

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

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

于 2018-05-05T22:02:39.283 回答