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)
问题:为什么不优化效果最终?
引入有效最终概念并没有影响有关常量表达式和字符串连接的规则。
请参阅Java® 语言规范,第 15.18.1 节。字符串连接运算符 +
执行不属于常量表达式 ( §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仅在某些用例中被特殊对待(如变量)
try-with-resource ( try(existingVariable) { … }(自 Java?9)引用它们但除此之外,它们不会被视为final变量。