Java 11 中角色的奇怪延迟投射行为
Weird Lazy Casting Behaviour for character in Java 11
有人可以解释一下我在下面说明的转换行为吗?
代码
char charo = (char) -1;
System.out.println(charo);
System.out.println((short) charo);
System.out.println((int) charo);
预期输出
?
-1
-1
实际输出
?
-1
65535
正如观察到的那样,当 -1 被转换为 char
并返回到 short
时,它会记住它是什么(即 -1),而当转换为 int
时它是 65535。我预计 charo 是 65535,因为在投射到 char
时下溢,因为 char
只包含正值。
我是否遗漏了某种懒惰的投射行为?引擎盖下发生了什么?
编辑 1:添加预期输出以说明我的误解
我找到原因了。事实证明,虽然看起来是这样,但并没有这样的行为。
charo
是 65535
的内幕。当它被转换为 short
时它仍然是 -1
不是因为它记得它是什么而是溢出的结果。
short
s 的最大值为 32767。65535 - 32767 = 32768
。通过从 -32768
开始并包括 32768
步数,我们再次到达 -1
。因此,它似乎记得它是什么,但事实并非如此。这都是引擎盖下的正常投射行为。
感谢 Johannes Kuhn 的评论。
int
值 -1
等同于 Two’s Complement 表示中的 0xFFFF_FFFF
。将其转换为 char
时,您将切断高位,以 0xFFFF
或更确切地说 '\uFFFF'
.
结尾
请务必记住,当您执行 System.out.println(charo);
时,您将以不同于其他打印语句的方法结束,因为 char
不仅具有不同的值范围比 short
或 int
,但语义也不同。
当您将 0xFFFF
转换为 short
时,值不会改变,但 0xFFFF
在 16 位 Two’s Complement 表示中恰好是 -1
.另一方面,当您将其转换为 int
时,该值将零扩展为 0x0000_FFFF
,等于 65535
.
这是根据 short
、char
和 int
数据类型来解释它的方式,但由于您还问了“幕后发生了什么?”,值得指出的是,这并不是 Java 实际工作的方式。
在Java中,所有涉及byte
、short
、char
或int
的算术都是使用int
完成的。即使是任何这些类型的局部变量实际上都是 int
字节码级别的变量。事实上,这同样适用于 boolean
变量,但是 Java 语言不允许我们利用它进行算术运算。
所以代码
char charo = (char)-1;
System.out.println(charo);
System.out.println((short)charo);
System.out.println((int)charo);
实际上编译为与
相同
int charo = (char)-1;
System.out.println(charo); // but invoking println(char)
System.out.println((short)charo);
System.out.println(charo);
或
int charo = 0x0000_FFFF;
System.out.println(charo); // but invoking println(char)
System.out.println((short)charo);
System.out.println(charo);
A开头说的,第一个println
结束在不同的方法,负责不同的语义。变量的 compile-time 类型仅在它使编译器 select 不同的方法时才重要。
当始终保持一个值的所有 32 位时,转换为 char
的效果是将高 16 位设置为零。所以 (char)-1
的结果是 0x0000_FFFF
并且这个操作甚至已经在 compile-time 完成了。所以第一条语句将常量0xFFFF
赋给一个变量。
下一条语句调用 println(char)
方法。调用方不涉及任何转换。
另外两个调用在 println(int)
处结束,在这里,转换为 short
实际上是在修改值。它具有 sign-extending short
值到 int
值的效果,这意味着第 15 位被复制到高 16 位。所以对于 0x...._FFFF
,第 15 位是 1,所以所有高位都设置为 1,最终在 0xFFFF_FFFF
,这是 int
值 -1
当使用 Two’s Complement.
最后的结果和上面给出的第一个解释一致,推理出char
、short
、int
的取值范围。对于很多场景,该级别的解释就足够了。但是您可能会注意到没有 println(short)
方法,因此要理解为什么 println(int)
足以打印 short
(或 byte
)值,有必要了解实际情况上。
有人可以解释一下我在下面说明的转换行为吗?
代码
char charo = (char) -1;
System.out.println(charo);
System.out.println((short) charo);
System.out.println((int) charo);
预期输出
?
-1
-1
实际输出
?
-1
65535
正如观察到的那样,当 -1 被转换为 char
并返回到 short
时,它会记住它是什么(即 -1),而当转换为 int
时它是 65535。我预计 charo 是 65535,因为在投射到 char
时下溢,因为 char
只包含正值。
我是否遗漏了某种懒惰的投射行为?引擎盖下发生了什么?
编辑 1:添加预期输出以说明我的误解
我找到原因了。事实证明,虽然看起来是这样,但并没有这样的行为。
charo
是 65535
的内幕。当它被转换为 short
时它仍然是 -1
不是因为它记得它是什么而是溢出的结果。
short
s 的最大值为 32767。65535 - 32767 = 32768
。通过从 -32768
开始并包括 32768
步数,我们再次到达 -1
。因此,它似乎记得它是什么,但事实并非如此。这都是引擎盖下的正常投射行为。
感谢 Johannes Kuhn 的评论。
int
值 -1
等同于 Two’s Complement 表示中的 0xFFFF_FFFF
。将其转换为 char
时,您将切断高位,以 0xFFFF
或更确切地说 '\uFFFF'
.
请务必记住,当您执行 System.out.println(charo);
时,您将以不同于其他打印语句的方法结束,因为 char
不仅具有不同的值范围比 short
或 int
,但语义也不同。
当您将 0xFFFF
转换为 short
时,值不会改变,但 0xFFFF
在 16 位 Two’s Complement 表示中恰好是 -1
.另一方面,当您将其转换为 int
时,该值将零扩展为 0x0000_FFFF
,等于 65535
.
这是根据 short
、char
和 int
数据类型来解释它的方式,但由于您还问了“幕后发生了什么?”,值得指出的是,这并不是 Java 实际工作的方式。
在Java中,所有涉及byte
、short
、char
或int
的算术都是使用int
完成的。即使是任何这些类型的局部变量实际上都是 int
字节码级别的变量。事实上,这同样适用于 boolean
变量,但是 Java 语言不允许我们利用它进行算术运算。
所以代码
char charo = (char)-1;
System.out.println(charo);
System.out.println((short)charo);
System.out.println((int)charo);
实际上编译为与
相同int charo = (char)-1;
System.out.println(charo); // but invoking println(char)
System.out.println((short)charo);
System.out.println(charo);
或
int charo = 0x0000_FFFF;
System.out.println(charo); // but invoking println(char)
System.out.println((short)charo);
System.out.println(charo);
A开头说的,第一个println
结束在不同的方法,负责不同的语义。变量的 compile-time 类型仅在它使编译器 select 不同的方法时才重要。
当始终保持一个值的所有 32 位时,转换为 char
的效果是将高 16 位设置为零。所以 (char)-1
的结果是 0x0000_FFFF
并且这个操作甚至已经在 compile-time 完成了。所以第一条语句将常量0xFFFF
赋给一个变量。
下一条语句调用 println(char)
方法。调用方不涉及任何转换。
另外两个调用在 println(int)
处结束,在这里,转换为 short
实际上是在修改值。它具有 sign-extending short
值到 int
值的效果,这意味着第 15 位被复制到高 16 位。所以对于 0x...._FFFF
,第 15 位是 1,所以所有高位都设置为 1,最终在 0xFFFF_FFFF
,这是 int
值 -1
当使用 Two’s Complement.
最后的结果和上面给出的第一个解释一致,推理出char
、short
、int
的取值范围。对于很多场景,该级别的解释就足够了。但是您可能会注意到没有 println(short)
方法,因此要理解为什么 println(int)
足以打印 short
(或 byte
)值,有必要了解实际情况上。