Java中的隐式转换是如何工作的?

34

我知道在Java中,整数字面量默认为int类型的,所以如果我写下像这样的代码:

byte byteValue = 2;
Java自动将字面值2(默认为int类型)转换为byte类型。如果我写成以下形式,同样适用:
byte byteValue = 4/2;

RHS被评估为int并隐式转换为byte。

但是为什么在以下两种情况下不会发生隐式转换呢?

int n1 = 4;
byte value = n1/2;

在这里的 OR

-> In this context, "OR"
byte n1 = 4;
byte value = n1/2;

我知道这两个例子中的右手边都被解析为一个int。但是为什么Java不像前两种情况那样隐式地将其转换为byte。只有在有文字时才会发生对更小数据类型的隐式转换吗?

4个回答

31

解释

让我们来看一下你的代码和一些修改后的例子:

// Example 1
byte byteValue = 2;

// Example 2
byte byteValue = 4/2;

// Example 3
byte byteValue = 2000;

// Example 4
byte byteValue = 500/2;

// Example 5
int n1 = 4;
byte byteValue = n1/2;

非损转换

对于示例3示例4示例5,您将会得到所提到的编译时错误。

首先,您在示例1到4中使用的简单数学运算是在编译时执行的。因此,Java会在编译时计算500 / 2并用基本上byte byteValue = 250;替换代码。

在Java中,字节的有效值范围是-128127。因此,任何超出该范围的值都不能直接作为byte使用,而需要进行显式转换。正因为如此,示例1示例2通过了。


窄化转换的信息丢失

要理解为什么其他部分会失败,我们必须研究Java语言规范(JLS),更具体地说是第5.1.3. 窄化原始转换5.2. 赋值上下文章节。

它指出,从intbyte的转换(如果超出了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/22,并将代码替换为基本上是byte byteValue = 2;,这在范围内。

所以你把n1 / 2变成了一个常量表达式,如之前在5.2. 赋值上下文中解释的那样。

你可以查看15.29. 常量表达式中对于常量表达式的详细要求。基本上,所有简单的东西都可以在原地轻松计算,而无需任何方法调用或其他花哨的东西。


4
这在Java语言规范的§5.2中有详细说明。该部分讨论了在赋值上下文中允许使用以下哪种内容,例如:byte byteValue = n1/2;

赋值上下文允许使用以下内容之一:

  • ……
  • ...(与问题无关的转换)

此外,如果表达式是类型为byte, short, char, 或 int常量表达式(§15.28):

  • 当变量的类型为byte, short, 或 char时,可以使用缩小原始类型转换,并且常量表达式的值可以表示为变量的类型。

intbyte的转换是一种缩小的原始类型转换。

在这些情况下,右侧的表达式都是常量表达式,即编译器可以在编译时计算的表达式:

byte byteValue = 2;
byte byteValue = 4/2;

因此,转换被应用并且代码可以编译。

您可以在§15.28中准确地看到什么构成常量表达式。如果一个表达式具有像n1/2中的非final变量,则它不是常量表达式。编译器不想分析/运行/追踪您的代码来找出n1的确切值。因此,转换不可用,代码无法编译。


4

来自文档

此外,如果表达式是类型为byte、short、char或int的常量表达式(§15.28)

如果变量的类型是byte、short或char,并且常量表达式的值可表示为变量类型,则可以使用缩小原始转换。

因此,对于您的前两个情况,值是常量,因此它的值可以表示为变量类型byte

byte byteValue = 2;
byte byteValue = 4/2;

对于后两种情况中的n1,这里的n1/2不是一个常量表达式,因此无法进行转换。因此,n1/2的值在变量byte的类型中无法表示。

int n1 = 4;
byte value = n1/2;

byte n1 = 4;
byte value = n1/2;

1
这在https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.2中有描述。
另外,如果表达式是byte、short、char或int类型的常量表达式(§15.28):
  • 当变量的类型为byte、short或char时,可以使用缩小原始转换,如果常量表达式的值可以表示为变量的类型。
  • 结果过大:

    byte byteValue = 100000000/2;
    
    error: incompatible types: possible lossy conversion from int to byte
    

    作为操作数的最终变量:

    final byte n1 = 4;
    byte value = n1/2;
    

    我不明白你所说的“final variables”是什么意思? - mohsin ahmed
    2
    在Java中有一个特殊的关键字final,用于表示不可重新分配的变量。请参见我的第二个例子。 - Lesiak
    实际上,仅将n1声明为final是不足以使n1/2成为编译时常量的。n1变量还需要是static的。 - Stephen C

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接