事实:
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
问题:为什么没有针对有效的决赛进行优化?
答案 0 :(得分:4)
有效最终概念的引入并未影响有关常量表达式和字符串连接的规则。
请参阅Java® Language Specification, §15.18.1. String Concatenation Operator +
引用的部分§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
变量)
final
- with-resource(try
(自Java9起)但除此之外,它们不会被视为try(existingVariable) { … }
变量。