我在工作的技术测试中遇到了这个问题。给出以下代码示例:
public class Manager {
public static void main (String args[]) {
System.out.println((int) (char) (byte) -2);
}
}
输出为65534。
此行为仅显示负值;0和正数产生相同的值,表示在SOP中输入的那个。此处的字节无关紧要;我尝试过没有它。
所以我的问题是:这到底是怎么回事?
我在工作的技术测试中遇到了这个问题。给出以下代码示例:
public class Manager {
public static void main (String args[]) {
System.out.println((int) (char) (byte) -2);
}
}
输出为65534。
此行为仅显示负值;0和正数产生相同的值,表示在SOP中输入的那个。此处的字节无关紧要;我尝试过没有它。
所以我的问题是:这到底是怎么回事?
System.out.println((int)(char)(byte)-130)
看看它是否只是“ 65536-130”。然后阅读@Chris K答案并解决!:)
byte
演员就可以重新运行它!
(byte)
确实改变了结果,所以情况有所不同。
Answers:
在您了解这里发生的事情之前,我们需要达成一些先决条件。了解以下要点后,剩下的就是简单的推论:
JVM中的所有原始类型都表示为一系列位。的int
类型是由32位,所表示char
和short
类型由16位和byte
类型由8位表示。
所有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
。请记住,所有这些仅是解释位模式的问题。您可以以不同的方式记下负数,但是负数的这种反转表示法非常方便,因为它允许进行一些相当快速的转换,我们稍后将在一个小示例中看到。
如前所述,这不适用于该char
类型。该char
类型表示Unicode字符,其非负“数值范围”为0
to 65535
。每个数字都引用一个16位Unicode值。
当之间进行转换int
,byte
,short
,char
和boolean
类型的JVM需要添加或截比特。
如果目标类型由比其转换的类型更多的位来表示,那么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实现此操作的细节的情况下,请注意,此模型允许通过便宜的shift操作执行转换,这显然是有利的。
如前所述,该规则的一个例外是扩展了一个char
无符号类型。由于我们说没有符号,因此也不需要反转符号,因此总是通过用填充附加位来应用从a进行的转换。的A转换到因此被执行为: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
当原始类型的位数比目标类型的位数多时,仅会切断附加位。只要原始值适合目标值,就可以正常工作,例如short
将a转换为a的情况如下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不会区分boolean
,byte
,short
,char
和int
类型。它们全部由相同的JVM类型表示,其中虚拟机仅模拟这些转换。在方法的操作数堆栈(即方法中的任何变量)上,所有已命名类型的值都占用32位。但是,对于任何JVM实现者都可以随意处理的数组和对象字段,情况并非如此。
(char) 65534
还是(char) 0xFFFE
代替(char) 0x65534
?
00 00 00 00 | => (byte) -1
这里有两件事要注意:
因此,将-2强制转换为int会给我们11111111111111111111111111111111110。只有负值才会发生这种情况。当我们将其缩小为一个char时,int被截断为
1111111111111110
最后,将1111111111111110转换为一个int进行零扩展,而不是一个1,因为该值现在被认为是正数(因为char只能是正数)。因此,加宽位使值保持不变,但与负值情况不同,其值保持不变。当以十进制打印时,该二进制值为65534。
byte
为16位char
会产生16位的-2的两个补数,从而解析为65534 int
?这都与两个补语有关吗?我的意思是,char
演员表中的1填充是如何完成的?
我认为最简单的解释方法就是将其分解为您执行的操作顺序
Instance | # int | char | # byte | result |
Source | 11 11 11 11 | 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | -2 |
byte |(11 11 11 11)|(11 11 11 11)|(11 11 11 11)| 11 11 11 10 | -2 |
int | 11 11 11 11 | 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | -2 |
char |(00 00 00 00)|(00 00 00 00)| 11 11 11 11 | 11 11 11 10 | 65534 |
int | 00 00 00 00 | 00 00 00 00 | 11 11 11 11 | 11 11 11 10 | 65534 |
因此,是的,当您以这种方式看待它时,字节转换是重要的(从学术上来讲),尽管结果是无关紧要的(喜欢编程,重要的动作可能不会产生明显的影响)。在保持符号的同时变窄和变宽的效果。在哪里,到char的转换变窄了,但没有扩大符号。
(请注意,我使用#表示Signed位,并且如前所述,char没有符号位,因为它是无符号值)。
我用括号来表示内部实际发生的事情。数据类型实际上存储在它们的逻辑块中,但是如果以int形式查看,它们的结果将是parens所象征的。
带符号的值总是随着带符号的位的值而变宽。无符号总是随着位的增加而加宽。
*因此,窍门(或陷阱)是从字节扩展到int,在加宽时保持有符号值。然后在触及到字符时就缩小了。然后,这将关闭带符号的位。
如果没有发生向int的转换,则该值为254。但是,确实如此,所以没有。
byte
投不改变的结果,并不意味着它没有做任何事情...