有效最终与最终-不同的行为 最终有效最终

问题描述

到目前为止,我认为有效的最终最终的是等效的,并且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在这两者之间起了重要的作用,我不确定为什么。

我读了其他主题,例如

,但它们没有详细说明。毕竟,从更广泛的意义上讲,它们似乎是等效的。但是深入研究,它们显然有所不同。

是什么原因导致这种行为,任何人都可以提供一些解释此问题的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

因此,字符串实习生在这里的行为也有所不同(我不想在实际代码中使用此代码段,只是对不同的行为感到好奇)。

解决方法

首先,我们仅谈论局部变量最终有效不适用于字段。这很重要,因为final字段的语义非常不同,并且需要进行大量的编译器优化和内存模型保证,请参阅$17.5.1关于最终字段的语义。

在表面级别finaleffectively final上局部变量的确相同。但是,JLS明确区分了这两者,而在这样的特殊情况下,它们实际上具有广泛的影响。


前提

来自JLS§4.12.4final个变量:

常量变量原始类型或类型为 String 且已初始化为 constant的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)应用于第二和第三操作数,条件表达式的类型为第二和第三操作数的提升类型。 / p>

与您执行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类型,其中Tbyteshortchar,而另一个操作数是 int类型的常量表达式§15.29),其值可以用类型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)共享唯一的实例。

在谈论文字时很容易忽略,但实际上它也适用于常量表达式。

,

另一方面是,如果在方法的主体中将变量声明为final,则其行为与作为参数传递的final变量会有不同的行为。

public void testFinalParameters(final String a,final String b) {
  System.out.println(a + b == "ab");
}

...
testFinalParameters("a","b"); // Prints false

同时

public void testFinalVariable() {
   final String a = "a";
   final String b = "b";
   System.out.println(a + b == "ab");  // Prints true
}

...
testFinalVariable();

之所以会这样,是因为编译器知道使用final String a = "a"的{​​{1}}变量将始终具有a的值,因此"a"a可以互换而无需问题。 以不同的方式,如果未定义"a"a或定义final但在运行时分配了它的值(如上例中的final参数),则编译器在使用之前一无所知。因此,连接发生在运行时,并且不使用内部缓冲池而生成了新的字符串。


基本上,行为是:如果编译器知道变量是常量,则可以将其使用与使用常量相同。

如果未将变量定义为final(或者它是final,但其值在运行时定义),则编译器没有理由将其视为常量,即使其值等于常量且其值从不改变了。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...