为什么 Java 要求对最终变量进行显式强制转换(如果它是从数组复制的)

2022-08-31 16:28:23

从以下代码开始...

byte foo = 1;
byte fooFoo = foo + foo;

当我尝试编译此代码时,我会收到以下错误...

错误:(5, 27) java: 不兼容的类型: 可能的有损转换从 int 到字节

...但如果是最终的...foo

final byte foo = 1;
final byte fooFoo = foo + foo;

文件将成功编译。

继续执行以下代码...

final byte[] fooArray = new byte[1];
fooArray[0] = 1;

final byte foo = fooArray[0];
fooArray[0] = 127;

System.out.println("foo is: " + foo);

...将打印

foo is: 1

...这很好。该值将复制到最终变量,并且无法再更改。使用数组中的值不会更改 的值(如预期...)。foo

为什么以下需要演员阵容?

final byte[] fooArray = new byte[1];
fooArray[0] = 1;
final byte foo = fooArray[0];
final byte fooFoo = foo + foo;

这与这个问题中的第二个例子有什么不同?为什么编译器给我以下错误?

错误:(5, 27) java: 不兼容的类型: 可能的有损转换从 int 到字节

怎么会这样?


答案 1

JLS (§5.2) 对使用常量表达式进行赋值转换有特殊规则:

此外,如果表达式是 、 、 或 类型的常量表达式 (§15.28):byteshortcharint

  • 如果变量的类型为 、 或 ,并且常量表达式的值在变量的类型中是可表示的,则可以使用缩小基元转换。byteshortchar

如果我们按照上面的链接,我们会在常量表达式的定义中看到这些:

  • 基元类型的文本和类型的文本String
  • 添加剂运算符和+-
  • 引用常量变量的简单名称 (§6.5.6.1) (§4.12.4)。

如果我们按照上面的第二个链接,我们会看到

基元类型或类型的变量,即使用编译时常量表达式 (§15.28) 进行初始化,称为常量变量Stringfinal

因此,只能将其赋值给 if 是一个常量变量。要将其应用于您的案例:foo + foofooFoofoo

  • byte foo = 1; 定义常量变量,因为它不是 。final

  • final byte foo = 1; 确实定义了一个常量变量,因为它是用常量表达式(原始文本)初始化的。final

  • final byte foo = fooArray[0]; 定义常量变量,因为它未使用常量表达式初始化。

请注意,是否本身并不重要。fooFoofinal


答案 2

值 1 非常适合一个字节;1+1也是如此;当变量最终确定时,编译器可以执行常量折叠。(换句话说:编译器在执行该 +操作时不使用;但“原始”1 值)foo

但是,当变量不是最终变量时,那么所有关于转换和升级的有趣规则都会启动(请参阅此处;您想阅读有关扩大原始转换的第5.12节)。

对于第二部分:使数组最终仍然允许您更改其任何字段;所以又一次;没有恒定的折叠可能;因此,“加宽”操作再次启动。