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

Posted

技术标签:

【中文标题】Java 中的隐式转换是如何工作的?【英文标题】:How does implicit conversion work in Java? 【发布时间】:2021-10-21 13:58:34 【问题描述】:

我知道在 Java 中整数文字默认是 int,所以如果我写这样的东西

byte byteValue = 2;

Java 自动将文字值 2(默认为 int)转换为字节。 如果我写,同样的事情也有效

byte byteValue = 4/2;

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

但是为什么下面两种情况没有发生隐式转换呢?

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

或在此

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

我知道这两个示例的 RHS 都被评估为 int。但是为什么Java不像前两种情况那样将其隐式转换为字节。是否只有在有字面量的情况下才会隐式转换为较小的数据类型?

【问题讨论】:

【参考方案1】:

来自doc:

此外,如果表达式是常量表达式(§15.28) 输入 byte、short、char 或 int:

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

所以对于你的前两种情况,值是常量,为什么它的值可以表示为变量类型byte

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

对于n1 的后两种情况,这里的n1/2 不是constant expression,因此无法进行转换。所以n1/2的值不能用变量byte的类型来表示。

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

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

【讨论】:

【参考方案2】:

说明

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

// 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. Narrowing Primitive Conversion 和 5.2. Assignment Contexts。

它表示从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 不能 100% 保证 n1 在执行 n1/2 之前的最后一秒没有改变。因此,它必须考虑你的所有代码,看看是否有人偷偷访问了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. Assignment Contexts 中所解释的那样。

您可以在15.29. Constant Expressions 中查看需要有一个常量表达式的详细信息。基本上所有简单的东西都可以轻松计算,无需任何方法调用或其他花哨的东西。

【讨论】:

【参考方案3】:

这在 Java 语言规范的 §5.2 中有记录。该部分讨论了在分配上下文中允许哪些转换,例如 byte byteValue = n1/2; 中的转换。

分配上下文允许使用以下之一:

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

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

如果变量的类型是byteshortchar,则可以使用缩小原语转换,并且常量表达式的值可以在变量的类型中表示。

intbyte 的转换是一种窄化原语转换。

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

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

因此应用了转换并编译了代码。

您可以在§15.28 中准确了解常量表达式的构成。你会看到,如果一个表达式有一个像n1/2 这样的非final 变量,它就不是一个常量表达式。编译器不想分析/运行/跟踪您的代码以找出n1 的确切值。因此,转换不可用,代码无法编译。

【讨论】:

【参考方案4】:

这在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 变量作为操作数:

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

【讨论】:

我不明白你所说的final variables是什么意思? Java中有一个特殊的关键字final,表示一个不能重新赋值的变量。看我的第二个例子 实际上,将n1 声明为final 不足以使n1/2 成为编译时间常数。 n1 变量也需要为 static

以上是关于Java 中的隐式转换是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

Scala中的隐式转换|理解

深入浅出JavaScript中的隐式转换

为啥 Java 和 C# 没有到布尔值的隐式转换?

第五篇:你“ 看不见 ” 的隐式转换

OracleSQL Server和MySQL的隐式转换异同

追踪 Python 2 中的隐式 unicode 转换