从以下代码开始…
byte foo = 1;
byte fooFoo = foo + foo;
当我尝试编译此代码时,会得到以下错误…
Error:(5, 27) java: incompatible types: possible lossy conversion from int to byte
…但如果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
…这很好。该值将被复制到最终变量,并且不能再更改。使用数组中的值不会更改EDOCX1的值(如预期的那样…)。
为什么以下内容需要演员表?
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
怎么会这样?
只需注意第二个示例,即从数组初始化foo,并测试它是否不变。即使foo不是final,它的值也不会改变。赋值(在本例中是初始化)复制发生时的值(1),即该值。无论最终结果是不是,fooArray[0]到127的后续变化不会自动传播到foo。
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并不重要。
但最后一个问题呢:"这怎么可能发生?"(此处=从int到byte的可能有损转换)
@实际上,在你的例子中,这是不可能发生的。但是编译器不知道这一点;规范不允许它进行这种推断。
值1非常适合一个字节;1+1也是如此;当变量是最终变量时,编译器可以进行常量折叠。(换句话说:编译器在执行+operation时不使用foo;而是使用"raw"1值)
但是当变量不是最终变量时,所有关于转换和升级的有趣规则都会出现(请参见这里;您想阅读第5.12节关于扩大原始转换的内容)。
第二部分:使一个数组成为最终数组仍然允许您更改它的任何字段;再次如此;不可能持续折叠;因此"加宽"操作再次开始。
这确实是编译器在与final一起使用时在持续折叠中所做的,正如我们从字节代码中看到的那样:
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,它还会抱怨:
final byte s = 127;
byte ss = s + s;
在这种情况下,编译器计算结果并知道它超出了限制,因此它仍然会抱怨它们不兼容。
更多:
还有一个关于用绳子不断折叠的问题:
我只有手机可以接,所以我跳过了字节码部分。很高兴你自愿这么做——)
同时也说明了其动机:当编译到字节码时,为了存储和计算,byte变量被扩大到int变量,除非右边的操作数是final常量,在这种情况下,计算可以在编译时静态进行。