在你们了解这里发生了什么之前,我们需要商定一些先决条件。通过理解以下要点,剩下的就是简单的推演:
JVM 中的所有基元类型都表示为一个位序列。类型由 32 位表示,和 类型由 16 位表示,类型由 8 位表示。int
char
short
byte
-
所有 JVM 编号都有符号,其中类型是唯一无符号的“数字”。当一个数字被符号时,最高位用于表示这个数字的符号。对于此最高位,表示非负数(正数或零)并表示负数。此外,对于有符号数字,负值(技术上称为二的补码表示法)被反转为正数的递增顺序。例如,正值以位表示,如下所示:char
0
1
byte
00 00 00 00 => (byte) 0
00 00 00 01 => (byte) 1
00 00 00 10 => (byte) 2
...
01 11 11 11 => (byte) Byte.MAX_VALUE
而负数的位顺序是颠倒的:
11 11 11 11 => (byte) -1
11 11 11 10 => (byte) -2
11 11 11 01 => (byte) -3
...
10 00 00 00 => (byte) Byte.MIN_VALUE
这种倒置的表示法也解释了为什么与正范围相比,负范围可以容纳额外的数字,后者包括数字的表示形式。请记住,所有这些都只是解释位模式的问题。您可以以不同的方式注意负数,但是负数的这种倒置表示法非常方便,因为它允许一些相当快速的转换,正如我们稍后在一个小示例中看到的那样。0
如前所述,这不适用于该类型。该类型表示一个 Unicode 字符,其非负“数字范围”为 to 。此数字中的每一个都引用一个 16 位 Unicode 值。char
char
0
65535
-
在 、 、 和 类型之间转换时,JVM 需要添加或截断位。int
byte
short
char
boolean
如果目标类型由比转换它的类型的位更多的位表示,则JVM只需用给定值(表示签名)的最高位的值填充其他插槽:
| short | byte |
| | 00 00 00 01 | => (byte) 1
| 00 00 00 00 | 00 00 00 01 | => (short) 1
由于倒置符号,此策略也适用于负数:
| short | byte |
| | 11 11 11 11 | => (byte) -1
| 11 11 11 11 | 11 11 11 11 | => (short) -1
这样,将保留值的符号。在不详细介绍为JVM实现此功能的情况下,请注意,此模型允许通过廉价的移位操作执行铸造,这显然是有利的。
此规则的一个例外是扩大一个类型,正如我们之前所说,它是无符号的。从 a 的转换总是通过填充额外的位来应用,因为我们说没有符号,因此不需要倒置符号。因此,将 a 转换为 a 时,将执行以下操作:char
char
0
char
int
| int | char | byte |
| | 11 11 11 11 | 11 11 11 11 | => (char) \uFFFF
| 00 00 00 00 | 00 00 00 00 | 11 11 11 11 | 11 11 11 11 | => (int) 65535
当原始类型的位数多于目标类型时,仅会切断其他位。只要原始值适合目标值,这就可以正常工作,例如,对于以下将 a 转换为 a 的操作:short
byte
| short | byte |
| 00 00 00 00 | 00 00 00 01 | => (short) 1
| | 00 00 00 01 | => (byte) 1
| 11 11 11 11 | 11 11 11 11 | => (short) -1
| | 11 11 11 11 | => (byte) -1
但是,如果值太大或太小,则不再起作用:
| short | byte |
| 00 00 00 01 | 00 00 00 01 | => (short) 257
| | 00 00 00 01 | => (byte) 1
| 11 11 11 11 | 00 00 00 00 | => (short) -32512
| | 00 00 00 00 | => (byte) 0
这就是为什么缩小铸件有时会导致奇怪的结果。您可能想知道为什么以这种方式实现缩小范围。你可以争辩说,如果JVM检查一个数字的范围,并且宁愿将一个不兼容的数字转换为同一符号的最大可表示值,那会更直观。但是,这将需要分支成本高昂的操作。这一点尤其重要,因为这两者的补码符号允许廉价的算术运算。
有了所有这些信息,我们可以看到您的示例中的数字发生了什么变化:-2
| int | char | byte |
| 11 11 11 11 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | => (int) -2
| | | 11 11 11 10 | => (byte) -2
| | 11 11 11 11 | 11 11 11 10 | => (char) \uFFFE
| 00 00 00 00 00 00 00 00 | 11 11 11 11 | 11 11 11 10 | => (int) 65534
如您所见,强制转换是多余的,因为强制转换会切割相同的位。byte
char
所有这些都由JVMS指定,如果您更喜欢所有这些规则的更正式的定义。
最后一点:类型的位大小不一定表示 JVM 为其内存中表示此类型而保留的位数。事实上,JVM 不区分 、 、 和类型。它们都由相同的JVM类型表示,其中虚拟机仅模拟这些转换。在方法的操作数堆栈(即方法中的任何变量)上,命名类型的所有值都消耗 32 位。然而,对于任何JVM实现者都可以随意处理的数组和对象字段,情况并非如此。boolean
byte
short
char
int