解释
让我们来看一下你的代码和一些修改后的例子:
byte byteValue = 2;
byte byteValue = 4/2;
byte byteValue = 2000;
byte byteValue = 500/2;
int n1 = 4;
byte byteValue = n1/2;
非损转换
对于示例3、示例4和示例5,您将会得到所提到的编译时错误。
首先,您在示例1到4中使用的简单数学运算是在编译时执行的。因此,Java会在编译时计算500 / 2
并用基本上byte byteValue = 250;
替换代码。
在Java中,字节的有效值范围是-128
到127
。因此,任何超出该范围的值都不能直接作为byte
使用,而需要进行显式转换。正因为如此,示例1和示例2通过了。
窄化转换的信息丢失
要理解为什么其他部分会失败,我们必须研究Java语言规范(JLS),更具体地说是第5.1.3. 窄化原始转换和5.2. 赋值上下文章节。
它指出,从int
到byte
的转换(如果超出了byte
的范围)是一种窄化原始转换,并且由于明显的原因,它可能会丢失信息。它继续解释了如何进行转换:
将有符号整数窄化转换为整数类型T只需丢弃除最低n位之外的所有位,其中n是用于表示类型T的位数。除了可能丢失关于数值大小的信息外,这还可能导致结果值的符号与输入值的符号不同。
从第二章开始,如果值是一个常量表达式,那么允许进行窄化转换的
作业。
此外,如果表达式是类型为byte
、short、char或int的常量表达式(§15.29):
如果变量的类型是byte
、short或char,并且常量表达式的值可以在变量的类型中表示,则可以使用窄化原始转换。
长话短说,可能会丢失信息的窄化转换(因为值超出范围)必须明确告知Java。Java不会自动执行此操作,除非你强制要求。这可以通过强制转换来实现。
所以举个例子:
byte byteValue = (byte) (500 / 2)
导致数值为
-6
。
常量表达式
你最后的例子非常有趣:
int n1 = 4;
byte byteValue = n1/2;
尽管这并未超出范围,但Java仍将其视为有损窄化转换。为什么会这样呢?
嗯,Java无法百分之百确保在执行
n1/2
之前,
n1
不会在最后一秒发生变化。因此,它必须考虑你的所有代码,看看是否有人偷偷访问并更改了
n1
。Java在编译时不进行这种分析。
所以,如果你能告诉Java,
n1
始终保持为
4
,并且实际上永远不会改变,那么它就可以成功编译。在这种特定情况下,将其声明为
final
就足够了。因此,使用
final int n1 = 4;
byte byteValue = n1/2;
它实际上会编译通过,因为Java知道n1
保持为4
,不再改变。因此,它可以在编译时计算n1/2
为2
,并将代码替换为基本上是byte byteValue = 2;
,这在范围内。
所以你把n1 / 2
变成了一个常量表达式,如之前在5.2. 赋值上下文中解释的那样。
你可以查看15.29. 常量表达式中对于常量表达式的详细要求。基本上,所有简单的东西都可以在原地轻松计算,而无需任何方法调用或其他花哨的东西。
final
,用于表示不可重新分配的变量。请参见我的第二个例子。 - Lesiakn1
声明为final
是不足以使n1/2
成为编译时常量的。n1
变量还需要是static
的。 - Stephen C