到目前为止,我认为有效的final和final或多或少是等价的,jls会对它们进行相似的处理,如果在实际行为中不完全相同的话。然后我发现了一个虚构的场景:
final int a = 97;
System.out.println(true ? a : 'c'); // outputs a
// versus
int a = 97;
System.out.println(true ? a : 'c'); // outputs 97
显然,jls在这两者之间有着重要的区别,我不知道为什么。
我读了其他的帖子,比如
最终和有效最终的区别
有效最终变量vs最终变量
一个变量是“有效的最终”是什么意思?
但他们没有详细说明。毕竟,在更广泛的层面上,它们似乎相当。但深入研究,他们显然有所不同。
是什么导致了这种行为,有人能提供一些jls定义来解释这一点吗?
编辑:我发现了另一个相关场景:
final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true
// versus
String a = "a";
System.out.println(a + "b" == "ab"); // outputs false
因此字符串interning在这里的行为也不同(我不想在实际代码中使用这个片段,只是好奇不同的行为)。
2条答案
按热度按时间xjreopfe1#
首先,我们只讨论局部变量。实际上,final不适用于字段。这一点很重要,因为
final
字段是非常不同的,并且需要经过大量的编译器优化和内存模型保证,请参阅$17.5.1关于final字段的语义。在地面上
final
以及effectively final
因为局部变量确实是相同的。然而,jls明确区分了这两种情况,在这种特殊情况下,这两种情况实际上有着广泛的影响。前提
来自jls§4.12.4关于
final
变量:常量变量是
final
用常量表达式初始化的基元类型或字符串类型的变量(§15.29). 变量是否为常量变量可能与类初始化有关(§12.4.1),二进制兼容性(§13.1),可达性(§14.22)和明确的转让(§16.1.1).自
int
是原语,变量a
就是这样一个常量变量。此外,从同一章关于
effectively final
:某些未声明为final的变量实际上被视为final:。。。
所以从措辞上看,很明显在另一个例子中,
a
不被认为是一个常量变量,因为它不是最终变量,而是有效的最终变量。行为
现在我们有了区别,让我们来看看发生了什么以及为什么输出不同。
您正在使用条件运算符
? :
在这里,我们必须检查它的定义。来自jls§15.25:有三种条件表达式,按第二和第三个操作数表达式分类:布尔条件表达式、数值条件表达式和引用条件表达式。
在本例中,我们讨论的是一个数值条件表达式,来自jls§15.25.2:
数值条件表达式的类型确定如下:
这就是这两个案子的不同分类。
最终有效
这个版本是
effectively final
与此规则匹配:否则,一般数字推广(§5.6)应用于第二个和第三个操作数,条件表达式的类型是第二个和第三个操作数的提升类型。
这和你的行为是一样的
5 + 'd'
,即。int + char
,导致int
. 见jls§5.6数值提升确定数值上下文中所有表达式的提升类型。选择提升类型,以便每个表达式都可以转换为提升类型,并且在算术运算的情况下,为提升类型的值定义运算。数值上下文中表达式的顺序对于数值提升并不重要。规则如下:
[...]
下一步,扩展基本体转换(§5.1.2)和缩小原语转换(§5.1.3)适用于某些表达式,根据以下规则:
在数字选择上下文中,以下规则适用:
如果任何表达式的类型为
int
不是一个常数表达式(§15.29),则升级类型为int
,以及其他不属于类型的表达式int
将基本体转换为int
.所以一切都升级为
int
作为a
是一个int
已经有了。这就解释了97
.最终
具有
final
变量与此规则匹配:如果其中一个操作数的类型为
T
哪里T
是byte
,short
,或char
,另一个操作数是常量表达式(§15.29)类型int
其值可在类型中表示T
,则条件表达式的类型为T
.最后一个变量
a
属于类型int
一个常量表达式(因为它是final
). 它可以表示为char
因此结果是char
. 这就结束了输出a
.字符串示例
字符串相等的示例基于相同的核心差异,
final
变量被视为常量表达式/变量,并且effectively final
不是。在java中,字符串内部处理是基于常量表达式的,因此
是
true
同时(不要在实际代码中使用这个构造)。见jls§3.10.5:
此外,字符串文本总是引用类string的同一示例。这是因为字符串文字-或者更一般地说,字符串是常量表达式的值(§15.29)-被“实习”以便使用该方法共享独特的示例
String.intern
(§12.5).很容易被忽视,因为它主要讨论的是文字,但它实际上也适用于常量表达式。
093gszye2#
另一个方面是,如果变量在方法体中声明为final,那么它的行为与作为参数传递的final变量不同。
虽然
这是因为编译器知道
final String a = "a"
这个a
变量将始终具有"a"
价值观a
以及"a"
可以互换而没有问题。不同的是,如果a
未定义final
或者它被定义了final
但是它的值是在运行时分配的(如上面的例子,final是a
参数)编译器在使用前什么都不知道。因此,连接在运行时发生,并生成一个新字符串,而不是使用intern池。基本上,这种行为是:如果编译器知道一个变量是一个常量,那么使用它的方式与使用常量的方式相同。
如果变量不是final定义的(或者它是final,但它的值是在运行时定义的),那么编译器也没有理由将它作为常量处理,如果它的值等于常量并且它的值从未更改。