当前位置: 首页 > 知识库问答 >
问题:

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

鲁淇
2023-03-14

到目前为止,我认为有效的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

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

共有2个答案

拓拔泉
2023-03-14

另一个方面是,如果变量在方法主体中被声明为 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();

发生这种情况是因为编译器知道使用< code>final String a = "a"时,< code>a变量将始终具有< code>"a"值,因此< code>a和< code>"a"可以毫无问题地互换。不同的是,如果< code>a未定义为< code>final或定义为< code>final,但其值是在运行时赋值的(如上面的示例,其中final是< code>a参数),编译器在使用它之前不知道任何事情。所以串联发生在运行时,并生成一个新的字符串,而不使用intern池。

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

如果变量未定义为final(或它是final,但其值在运行时定义),编译器也没有理由将其作为常量处理,如果它的值等于常量且其值从未更改。

劳韬
2023-03-14

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

从表面上看,局部变量的最终和有效的最终确实是相同的。然而,JLS明确区分了两者,这在像这样的特殊情况下实际上具有广泛的影响。

根据JLS§4.12.4中关于<代码>最终 变量的规定:

常量变量是使用常量表达式 (§15.29) 初始化的基元类型或 String 类型的最终变量。变量是否为常量变量可能会对类初始化 (§12.4.1)、二进制兼容性 (§13.1)、可访问性 (§14.22) 和确定赋值 (§16.1.1) 产生影响。

由于<code>int

此外,从关于有效最终的同一章中:

某些未声明为final的变量实际上被视为final:。。。

因此,从措辞的方式来看,很明显,在另一个例子中,a不被认为是一个常数变量,因为它不是最终的,而只是有效的最终变量。

现在我们已经有了区别,让我们来看看发生了什么以及为什么输出是不同的。

您正在使用条件运算符 ? : 这里,因此我们必须检查其定义。根据 JLS§15.25:

有三种条件表达式,根据第二和第三操作数表达式进行分类:布尔条件表达式、数值条件表达式和引用条件表达式。

在本例中,我们谈论的是 JLS§15.25.2 中的数字条件表达式:

数值条件表达式的类型确定如下:

这是两个案例被不同分类的部分。

< code >有效的最终版本与此规则匹配:

否则,一般数字提升(§5.6)应用于第二个和第三个操作数,条件表达式的类型是第二个和第三个操作数的提升类型。

这与执行 5 个 'd'(即 int char)的行为相同,从而导致 int。参见 JLS§5.6

数值提升确定数值上下文中所有表达式的提升类型。提升类型的选择使得每个表达式都可以转换为提升类型,并且在算术运算的情况下,操作是为提升类型的值定义的。数值上下文中表达式的顺序对于数值提升并不重要。规则如下:

[...]

接下来,根据以下规则,扩大原语转换(§5.1.2)和缩小原语转换(§5.1.3)应用于某些表达式:

在数字选择上下文中,以下规则适用:

如果任何表达式的类型为< code>int,并且不是常量表达式(15.29),则提升的类型为< code>int,其他非< code>int类型的表达式将进行扩展原语转换,转换为< code>int。

因此,所有内容都升级为<code>int

带有Final变量的版本由以下规则匹配:

如果其中一个操作数是T类型,其中Tbytechar,而另一个操作数是int类型的常量表达式(§15.29),其值可在T类型中表示,则条件表达式的类型是T

最后一个变量<code>a)。它可以表示为<code>char</code>,因此结果是<code>char</code>类型。输出<code>a</code>到此结束。

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

在Java中,字符串内部是基于常量表达式的,因此

"a" + "b" + "c" == "abc"

istrue也是如此(不要在实际代码中使用此构造)。

参见 JLS§3.10.5:

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

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

 类似资料:
  • 我正在使用RxVertx,它是一种RxJava和Java8,我有一个编译错误。 这是我的代码: 编译错误是:“在封闭范围内定义的局部变量游戏必须是最终的或有效的最终的” 我无法将“game”定义为final,因为我在函数末尾执行分配\set并返回它。 如何编译此代码? 谢了。

  • 在Java8中,Java设计者提出了一个有效的final变量的概念,即一个如果被“final”追加就不会给编译器带来错误的变量。我的问题是,这个新提出的“有效最终”概念比经典的“最终”提供了什么?作为一名Java开发人员,我实际上得到了什么好处?

  • 编译器所做的是复制该变量,就像它是通过构造函数传递的一样。我上了这三节课: 1: 2: 为什么实现者不能直接复制变量,不管它是否被修改,所以我们可以这样做:

  • 问题内容: 我正在尝试将java8 forEach循环内的布尔变量更改为true,这是非最终的。但是我遇到了以下错误:在封闭范围内定义的必需局部变量必须是final或有效的final。 如何解决这个错误? 代码: 这是我在函数中创建的变量。 现在,当我尝试更改它时: 我收到错误消息:封闭范围中定义的必需局部变量必须是final或有效的final。 为什么会出现此错误,以及如何解决? 问题答案: 您

  • 我在Java8中使用lambdas,我遇到了警告,从lambda表达式引用的。我知道当我在匿名类中使用变量时,它们在外部类中必须是final的,但final和有效的final有什么区别?

  • 问题内容: 我的问题很简单: 编译器是否将final类中的所有方法都视为final本身?将关键字添加到最终类中的方法是否有效果? 我知道最终方法更有可能被内联,这就是我要问的原因。 提前致谢。 问题答案: 没错,final类中的所有方法都隐式为final。 看这里: “请注意,您也可以将整个类声明为final。声明为final的类不能被子类化。例如,在创建不可变类(如String类)时,这特别有用