我知道
>>>
是无符号右移,并且由于Java中的byte
是有符号的8位,所以-1将表示为1111 1111
。结果,对于下面的代码:
byte b = (byte) -1;
b >>>= 1;
System.out.println(b);
它不应该打印
127
(二进制为0111 1111)吗?但无论我执行 -1
到 >>>
多少次(例如循环),我得到的结果始终是 b
。
任何人都可以解释为什么以及以什么方式我可以获得我期望的结果(即执行无符号右移,其中
-1
右移一次等于Java中127
的byte
)?我的理解有错吗?非常感谢!
您遇到了一系列非常奇怪的 Java 设计选择,这使得这段代码变得很糟糕。让我们来分解一下:
byte
、short
、char
和boolean
较差在java中,
byte
、short
、char
、boolean
是inferior原语,int
、long
、double
和float
是superior基元。除了数组存储和数组加载之外,没有其他操作码。 java中没有将2个字节加在一起的操作码。它只是不存在。。鉴于 javac
不知道要生成什么,这实际上是不可能的。所以,相反:
byte b = 5;
byte c = 10;
byte d = b + c;
实际上意味着:
byte b = 5;
byte c = 10;
byte d = (int) b + (int) c;
因此,这是一个编译器错误 -
int + int
的结果是 int
,并且如果没有显式转换,则不允许尝试将 int
分配给 byte
。
您可以轻松查看任何java字节码列表。您会找到 FADD、DADD、IADD 和 LADD。但你找不到 BADD、CADD 或 SADD。
这个:
a += b;
实际上与a = a + b;
根本不一样!我知道你是这么教的,但这是不正确的。事实上,它等于 a = (byte) (a + b);
,其中 byte
是 a 的类型。见证人:
double d = 5.5;
int x = 5;
x = x + d; // obviously, compiler error. Explicit cast needed.
对:
double d = 5.5;
int x = 5;
x += d;
完全合法。结果 x 为 10。完整流程为:
double
,因为 java 只能在两个相同类型的事物之间进行数学计算。d
是5.5,已经是双倍了,不错。DADD
字节码被执行;结果是 10.5.int
,生成 10
,并将其分配给 x
。如果你想看到这个,请运行
javap
。
因此,让我们分解您的代码:
byte b = (byte) -1;
这使得 int -1(32 个“1”位),然后将其转换为字节,导致
b
具有值 1111 1111
。到目前为止,一切都很好,没有什么令人惊讶的。
b >>>= 1;
呼男孩。这看起来很简单,但是却非常复杂。它的语法糖是:
b = (byte) (((int) b) >>> 1);
要打破那个:
(int) b
结果为 32 个 1 位。 b
的字节数为 -1,将其转换为 int 会产生 -1 int,即 32 个 1 位。>>> 1
然后将其设为 0,后跟 31 个 1 位。基本上不要使用
byte
。除非在以下情况下,否则不要使用劣质基元:
byte[]
很有用,而且每个项目确实只占用 1 个字节。对于所有其他事情(例如单个字段和针对数学的局部变量,例如您正在做的事情),不要。正如您在这里发现的那样,他们的行为并不像您想象的那样。
只需使用整数即可。如果您想要一个“无符号字节”,只需创建一个
int
变量并使用它:
byte b = (byte) -1;
int x = b & 0xFF; // x is now 0000...0000 1111 1111.
System.out.println(x); // prints 255
x >>= 1;
System.out.println(x); // prints 127
byte
作为单个值(不是 byte[]
- 只是 byte
)在 64 位架构上,在任何方面都不会比 int
甚至可能比
long
更高效。 Java 不会分配更少的内存(因为所有东西都必须在字边界上对齐),操作速度并不会更快(CPU 已经在 64 位块中工作)。