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:添加预期输出以说明我的误解

我找到原因了。事实证明,虽然看起来是这样,但并没有这样的行为。

charo65535 的内幕。当它被转换为 short 时它仍然是 -1 不是因为它记得它是什么而是溢出的结果。

shorts 的最大值为 32767。65535 - 32767 = 32768。通过从 -32768 开始并包括 32768 步数,我们再次到达 -1。因此,它似乎记得它是什么,但事实并非如此。这都是引擎盖下的正常投射行为。

感谢 Johannes Kuhn 的评论。

int-1 等同于 Two’s Complement 表示中的 0xFFFF_FFFF。将其转换为 char 时,您将切断高位,以 0xFFFF 或更确切地说 '\uFFFF'.

结尾

请务必记住,当您执行 System.out.println(charo); 时,您将以不同于其他打印语句的方法结束,因为 char 不仅具有不同的值范围比 shortint,但语义也不同。

当您将 0xFFFF 转换为 short 时,值不会改变,但 0xFFFF 在 16 位 Two’s Complement 表示中恰好是 -1 .另一方面,当您将其转换为 int 时,该值将零扩展为 0x0000_FFFF,等于 65535.

这是根据 shortcharint 数据类型来解释它的方式,但由于您还问了“幕后发生了什么?”,值得指出的是,这并不是 Java 实际工作的方式。

在Java中,所有涉及byteshortcharint的算术都是使用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.

最后的结果和上面给出的第一个解释一致,推理出charshortint的取值范围。对于很多场景,该级别的解释就足够了。但是您可能会注意到没有 println(short) 方法,因此要理解为什么 println(int) 足以打印 short(或 byte)值,有必要了解实际情况上。