有效的final与final-不同的行为

mlmc2os5  于 2021-07-05  发布在  Java
关注(0)|答案(2)|浏览(305)

到目前为止,我认为有效的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在这里的行为也不同(我不想在实际代码中使用这个片段,只是好奇不同的行为)。

xjreopfe

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 哪里 Tbyte , 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)-被“实习”以便使用该方法共享独特的示例 String.intern (§12.5).
很容易被忽视,因为它主要讨论的是文字,但它实际上也适用于常量表达式。

093gszye

093gszye2#

另一个方面是,如果变量在方法体中声明为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" 这个 a 变量将始终具有 "a" 价值观 a 以及 "a" 可以互换而没有问题。不同的是,如果 a 未定义 final 或者它被定义了 final 但是它的值是在运行时分配的(如上面的例子,final是 a 参数)编译器在使用前什么都不知道。因此,连接在运行时发生,并生成一个新字符串,而不是使用intern池。
基本上,这种行为是:如果编译器知道一个变量是一个常量,那么使用它的方式与使用常量的方式相同。
如果变量不是final定义的(或者它是final,但它的值是在运行时定义的),那么编译器也没有理由将它作为常量处理,如果它的值等于常量并且它的值从未更改。

相关问题