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

Zab*_*uza 103 java final jls language-lawyer effectively-final

到目前为止,我认为有效的 finalfinal或多或少是等效的,如果在实际行为中不相同,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
Run Code Online (Sandbox Code Playgroud)

显然,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
Run Code Online (Sandbox Code Playgroud)

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

Zab*_*uza 65

首先,我们只讨论局部变量实际上 final不适用于字段。这很重要,因为final字段的语义非常不同,并且受到大量编译器优化和内存模型承诺的影响,请参阅$17.5.1最终字段的语义。

在表面上finaleffectively 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

所以一切都被提升到intaint已。这解释了97.

最终的

带有final变量的版本通过此规则匹配:

如果其中一个操作数的类型为Twhere Tis byte, short, or char,而另一个操作数是其值可在 type 中表示的类型的常量表达式(第15.29 节),则条件表达式的类型为。intTT

最终变量a是类型int和常量表达式(因为它是final)。它可表示为char,因此结果的类型为char。输出到此结束a


字符串示例

字符串相等的示例基于相同的核心差异,final变量被视为常量表达式/变量,而effectively final不是。

在 Java 中,字符串实习基于常量表达式,因此

"a" + "b" + "c" == "abc"
Run Code Online (Sandbox Code Playgroud)

也是true(不要在实际代码中使用这个结构)。

JLS§3.10.5

此外,字符串字面量始终引用类 String 的同一个实例。这是因为字符串文字——或者更一般地说作为常量表达式(第15.29 节)的值的字符串——被嵌入”,以便使用方法String.intern(第12.5 节)共享唯一实例。

容易被忽视,因为它主要讨论的是文字,但它实际上也适用于常量表达式。

  • 确实,这是一个明确定义的怪癖,但 `a + "b" == "ab"` 仍然是一个**错误的表达式**。即使您**知道** `a` 是一个*常量*,不调用 `equals()` 也很容易出错。或者也许“脆弱”是一个更好的词,即将来维护代码时很可能崩溃。 (10认同)
  • 问题是你会期望“...”?a : 'c'` 无论 `a` 是*变量*还是*常量*,其行为都相同。从表述上看,并没有什么问题。--- 相比之下, `a + "b" == "ab"` 是一个**糟糕的表达式**,因为需要使用 `equals()` 来比较字符串([How do I Compare strings in Java?] (/sf/ask/35968271/))。事实上,当 `a` 是一个*常量*时,它“意外地”起作用,这只是字符串文字驻留的一个怪癖。 (8认同)
  • @Andreas 是的,但请注意,**字符串驻留**是 Java 的一个明确定义的功能。这不是巧合,明天或在不同的 JVM 中可能会发生变化。在任何有效的 Java 实现中,`"a" + "b" + "c" == "abc"` 必须为 `true`。 (5认同)
  • 请注意,即使在有效最终变量的主要域中,即它们在 lambda 表达式中的使用,差异也可能会改变运行时行为,即它可以在捕获和非捕获 lambda 表达式之间产生差异,后者评估为单例,但前者产生一个新对象。换句话说,`(final) String str = "a"; Stream.of(空,空)。<Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(当“str”(不是)“final”时,Runnable::run);` 会更改其结果。 (2认同)

Dav*_*INO 7

另一个方面是,如果变量在方法体中被声明为 final,它与作为参数传递的 final 变量的行为不同。

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

...
testFinalParameters("a", "b"); // Prints false
Run Code Online (Sandbox Code Playgroud)

尽管

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

...
testFinalVariable();
Run Code Online (Sandbox Code Playgroud)

这是因为编译器知道使用final String a = "a"a变量总是具有"a"使价值a"a"可以毫无问题地互换。不同的是,如果a未定义final或已定义final但在运行时分配了其值(如上例中 final 是a参数),编译器在使用前不知道任何内容。因此连接发生在运行时并生成一个新字符串,而不是使用实习池。


基本上行为是:如果编译器知道一个变量是一个常量,可以像使用常量一样使用它。

如果变量不是 final 定义的(或者它是 final 但它的值是在运行时定义的),那么编译器没有理由将它作为常量处理,如果它的值等于常量并且它的值永远不会改变。

  • 这里使用参数是不必要的混淆。你可以只做`final String a; a = "a";` 并获得相同的行为 (6认同)
  • 应用于参数的“finel”关键字与应用于局部变量的“final”具有不同的语义,等等...... (5认同)
  • 这没有什么奇怪的:) (4认同)
  • 这是问题的另一个方面。 (2认同)