如果从数组中复制了Java,为什么Java需要对最终变量进行显式强制转换?

Why does Java require an explicit cast on a final variable if it was copied from an array?

从以下代码开始…

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

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

Error:(5, 27) java: incompatible types: possible lossy conversion from int to byte

…但如果foo是最终的…

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

文件将成功编译。

继续执行以下代码…

1
2
3
4
5
6
7
final byte[] fooArray = new byte[1];
fooArray[0] = 1;

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

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

…将打印

1
foo is: 1

…这很好。该值将被复制到最终变量,并且不能再更改。使用数组中的值不会更改EDOCX1的值(如预期的那样…)。

为什么以下内容需要演员表?

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

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

Error:(5, 27) java: incompatible types: possible lossy conversion from int to byte

怎么会这样?


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

In addition, if the expression is a constant expression (§15.28) of type byte, short, char, or int:

  • A narrowing primitive conversion may be used if the type of the variable is byte, short, or char, and the value of the constant expression is representable in the type of the variable.

如果我们遵循上面的链接,我们可以在常量表达式的定义中看到:

  • Literals of primitive type and literals of type String
  • The additive operators + and -
  • Simple names (§6.5.6.1) that refer to constant variables (§4.12.4).

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

A variable of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28), is called a constant variable.

因此,如果foo是一个常量变量,那么foo + foo只能分配给fooFoo。要将其应用于您的案例:

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

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

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

注意,fooFoo本身是否是final并不重要。


值1非常适合一个字节;1+1也是如此;当变量是最终变量时,编译器可以进行常量折叠。(换句话说:编译器在执行+operation时不使用foo;而是使用"raw"1值)

但是当变量不是最终变量时,所有关于转换和升级的有趣规则都会出现(请参见这里;您想阅读第5.12节关于扩大原始转换的内容)。

第二部分:使一个数组成为最终数组仍然允许您更改它的任何字段;再次如此;不可能持续折叠;因此"加宽"操作再次开始。


这确实是编译器在与final一起使用时在持续折叠中所做的,正如我们从字节代码中看到的那样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    byte f = 1;
    // because compiler still use variable 'f', so `f + f` will
    // be promoted to int, so we need cast
    byte ff = (byte) (f + f);
    final byte s = 3;
    // here compiler will directly compute the result and it know
    // 3 + 3 = 6 is a byte, so no need cast
    byte ss = s + s;
    //----------------------
    L0
    LINENUMBER 12 L0
    ICONST_1 // set variable to 1
    ISTORE 1 // store variable 'f'
    L1
    LINENUMBER 13 L1
    ILOAD 1 // use variable 'f'
    ILOAD 1
    IADD
    I2B        
    ISTORE 2 // store 'ff'
    L2

    LINENUMBER 14 L2
    ICONST_3 // set variable to 3
    ISTORE 3 // store 's'
    L3
    LINENUMBER 15 L3
    BIPUSH 6 // compiler just compute the result '6' and set directly
    ISTORE 4 // store 'ss'

如果您将最后一个字节更改为127,它还会抱怨:

1
2
    final byte s = 127;
    byte ss = s + s;

在这种情况下,编译器计算结果并知道它超出了限制,因此它仍然会抱怨它们不兼容。

更多:

还有一个关于用绳子不断折叠的问题: