为什么 Java 负字节的无符号位移如此奇怪?

Why Java unsigned bit shifting for a negative byte is so strange?

我有一个字节变量:

byte varB = (byte) -1; // binary view: 1111 1111

我想查看最左边的两位并执行 6 位的无符号右移:

varB = (byte) (varB >>> 6);

但是我得到 -1 就好像它是 int 类型一样,并且得到 3 只有我换30!

我怎样才能解决这个问题并仅通过 6 位移位获得结果?

原因是与移位时发生的 int 的数字提升相关的符号扩展。值 varB 在移位前被提升为 int。确实发生了向右的无符号位移位,但是当转换回 byte 时,它的效果会下降,它只保留最后 8 位:

varB (byte)     : 11111111
promoted to int : 11111111 11111111 11111111 11111111
shift right 6   : 00000011 11111111 11111111 11111111
cast to byte    : 11111111

您可以使用按位与运算符 & 在移位之前屏蔽不需要的位。使用 0xFF 的位与运算仅保留 8 个最低有效位。

varB = (byte) ((varB & 0xFF) >>> 6);

这是现在发生的事情:

varB (byte)     : 11111111
promoted to int : 11111111 11111111 11111111 11111111
bit-and mask    : 00000000 00000000 00000000 11111111
shift right 6   : 00000000 00000000 00000000 00000011
cast to byte    : 00000011

因为这就是 java 中字节的 shifting 在语言中定义的方式:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.19.

要点是小于 int 的类型被默默地加宽为 int,移位然后变窄。

这使得你的单行有效地等同于:

byte b = -1;      // 1111_1111
int temp = b;     // 1111_1111_1111_1111_1111_1111_1111_1111
temp >>>= 6;      // 0000_0011_1111_1111_1111_1111_1111_1111
b = (byte) temp;  // 1111_1111

要仅移动字节,您需要自己使用无符号语义明确地进行扩大转换(并且也需要手动进行缩小转换):

byte b = -1;          // 1111_1111
int temp = b & 0xFF;  // 0000_0000_0000_0000_0000_0000_1111_1111
temp >>>= 6;          // 0000_0000_0000_0000_0000_0000_0000_0011
b = (byte) temp;      // 0000_0011