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 中字节的有效值为-128
到127
。因此,任何超出该范围的值都不能仅仅被视为byte
,而是需要显式转换。因此,示例 1 和 示例 2 通过。
有损缩小转换
要了解其余部分失败的原因,我们必须研究 Java 语言规范 (JLS),更具体的章节 5.1.3. Narrowing Primitive Conversion 和 5.2. Assignment Contexts。
它表示从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 不能 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;
中的转换。
分配上下文允许使用以下之一:
... ...(与问题无关的转换)此外,如果表达式是
如果变量的类型是byte
、short
、char
或int
类型的常量表达式(§15.28):byte
、short
或char
,则可以使用缩小原语转换,并且常量表达式的值可以在变量的类型中表示。
int
到byte
的转换是一种窄化原语转换。
在这些情况下,右侧的表达式都是常量表达式,即编译器可以在编译时计算的表达式:
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 中的隐式转换是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章