首先,我们只讨论关于局部变量的问题。 有效地被声明为 final 不适用于字段。这一点很重要,因为对于final
字段的语义是非常明确的,并且受到编译器优化和内存模型承诺的重大影响,请参见$17.5.1有关最终字段语义的内容。
在表面上看,局部变量的final
和有效地被声明为 final
确实是相同的。但是,JLS在两者之间做出了明确的区分,这实际上在特殊情况下产生了广泛的影响。
前提条件
根据JLS§4.12.4关于final
变量的说明:
常量变量是一个final
基本类型或String
类型变量,它被初始化为一个常量表达式§15.29。一个变量是否为常量变量可能会涉及到类初始化§12.4.1、二进制兼容性§13.1、可达性§14.22和明确赋值§16.1.1方面的问题。
由于int
是原始类型,变量a
是一个常量变量。
此外,在关于effectively final
的同一章节中:
某些未声明为final的变量被视为有效地final:...
因此,从这种措辞方式可以清楚地看出,在另一个示例中,a
不被认为是一个常量变量,因为它不是final,而仅仅是有效地final。
行为
既然我们有了区别,让我们查找一下正在发生什么以及为什么输出不同。
你在这里使用了条件运算符? :
,因此我们必须检查它的定义。来自JLS§15.25:
根据第二个和第三个操作数表达式分类,有三种条件表达式:布尔条件表达式、数值条件表达式和引用条件表达式。
在这种情况下,我们谈论的是一个数值条件表达式,来自JLS§15.25.2:
数值条件表达式的类型如下确定:
这就是两种情况被分类不同的部分。
有效地final
版本有效地final
与此规则匹配:
否则,对第二个和第三个操作数应用一般的数值提升(§5.6),并且条件表达式的类型是第二个和第三个操作数的提升类型。
这与执行
5 + 'd'
时的行为相同,即
int + char
,其结果为
int
。请参见
JLS§5.6。
数字提升决定了数值上下文中所有表达式的提升类型。选择提升类型的原则是每个表达式都可以转换为提升类型,并且在算术运算的情况下,该操作对提升类型的值是有定义的。数字上下文中表达式的顺序对于数字提升来说不重要。规则如下:
[...]
接下来,根据以下规则将
§5.1.2扩展原始转换和
§5.1.3缩小原始转换应用于某些表达式:
在数字选择上下文中,遵循以下规则:
如果任何表达式的类型为
int
并且不是常量表达式(
§15.29),则提升类型为
int
,而不是
int
类型的其他表达式会被转换为
int
进行扩展原始转换。
所以,由于
a
已经是
int
类型,因此所有内容都被提升为
int
。这就解释了输出结果
97
。
使用
final
变量的版本符合以下规则:
如果一个操作数是
T
类型(其中
T
是
byte
、
short
或
char
),另一个操作数是
常量表达式(
§15.29)且类型为
int
,它的值可以表示为
T
类型,则条件表达式的类型为
T
。
最终变量
a
是
int
类型,并且是常量表达式(因为它是
final
)。它可以表示为
char
,因此结果的类型为
char
。这就是输出结果
a
的原因。
字符串示例
字符串相等性示例基于同样的核心差异,final
变量被视为常量表达式/变量,而effectively final
不是。
在Java中,字符串池化是基于常量表达式的,因此
"a" + "b" + "c" == "abc"
true
也是这样的(在实际代码中不要使用这种构造方式)。
详见JLS§3.10.5:
此外,字符串字面值总是引用类String的同一实例。这是因为字符串字面值或者更一般地,作为常量表达式的值(§15.29)的字符串,会被“interned”以共享唯一实例,使用方法String.intern
(§12.5)。
虽然主要讨论了字面值,但它实际上也适用于常量表达式,很容易被忽视。
byte
、short
或char
,另一个操作数是类型为int
且值可在T类型中表示的常量表达式,则条件表达式的类型为T。”甚至在伯克利还发现了一个Java 1.0文档。相同的文本。是的,它一直保持那样。 - Andreas