为什么当 b 是一个字节且在 Java 中值为 -1 时,b >>> 1 总是等于 -1?

K_K*_*K_K 3 java binary byte bit-shift bitwise-operators

我知道这>>>是无符号右移,并且由于byte在 Java 中是有符号的 8 位,因此 -1 将表示为1111 1111。结果,对于下面的代码:

byte b = (byte) -1;
b >>>= 1;
System.out.println(b);

Run Code Online (Sandbox Code Playgroud)

它不应该打印127(二进制的0111 1111)吗?-1但无论我执行多少次(例如在循环中),>>>b得到的结果始终是。

任何人都可以解释为什么以及以什么方式我可以获得我期望的结果(即执行无符号右移,其中-1右移一次等于Java中的127a byte)?我的理解有错吗?多谢!

rzw*_*oot 10

您遇到了一系列非常奇怪的 Java 设计选择,这些选择使这段代码“撒谎”(它似乎做了一件事,但实际上不会做那件事)。让我们来分解一下:

鲜为人知的 java 方面 1:byteshortcharboolean较差

在java中,byte, short, char,boolean下级原语,int, long,doublefloat上级原语。除了数组存储和数组加载之外,没有其他操作码。java中没有将2个字节加在一起的操作码。它只是不存在。javac鉴于不知道要生成什么,因此实际上不可能做到这一点。所以与其:

byte b = 5;
byte c = 10;
byte d = b + c;
Run Code Online (Sandbox Code Playgroud)

实际上的意思是:

byte b = 5;
byte c = 10;
byte d = (int) b + (int) c;
Run Code Online (Sandbox Code Playgroud)

因此,这是一个编译器错误 - int + intis的结果int,并且在没有显式转换的情况下不允许尝试将 an 分配int给 a 。byte

您可以轻松地查看任何Java 字节码列表。您将找到 FADD、DADD、IADD 和 LADD(分别为“浮点加法”、“双倍加法”、“整数加法”和“长加法”)。但你找不到 byte/short/char 变体;没有 BADD、CADD 或 SADD。

鲜为人知的 java 方面 2:复合赋值运算符自动转换。

这: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.
Run Code Online (Sandbox Code Playgroud)

相对:

double d = 5.5;
int x = 5;
x += d;
Run Code Online (Sandbox Code Playgroud)

完全合法。结果 x 为 10。完整流程为:

  • x 是 5。这被转换为,double因为 java 只能在相同类型的两个事物之间进行数学运算。
  • d是5.5,已经是双倍了,不错。
  • DADD字节码被执行;结果是 10.5。
  • 这被转换为int,导致10,被分配给x

javap如果你想看到这个就跑吧。

结合两者

因此,让我们分解您的代码:

byte b = (byte) -1;
Run Code Online (Sandbox Code Playgroud)

这使得 int -1(32 个“1”位),然后将其转换为 byte,从而b得到值1111 1111。到目前为止,一切都很好,没有什么令人惊讶的。

b >>>= 1;
Run Code Online (Sandbox Code Playgroud)

呼男孩。这看起来很简单,但是却非常复杂。它的语法糖用于:

b = (byte) (((int) b) >>> 1);
Run Code Online (Sandbox Code Playgroud)

分解一下

  • (int) b结果为 32 个 1 位。b以字节为单位为 -1,将其转换为 int 会产生 -1 int,即 32 个 1 位。
  • >>> 1然后将其设为 0,后跟 31 个 1 位。
  • 然后将其转换回一个字节,即 -1 - 8 1 位。

那么我该怎么做呢?

byte基本上不要使用。除非在以下情况下,否则不要使用劣质基元:

  • 本质上,您可以在签名(即方法的返回类型或参数类型)中将它们用作 API 文档。
  • 你做一个数组。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
Run Code Online (Sandbox Code Playgroud)

byte作为一个单一值(不仅仅是byte[]byte,在任何方面都不比64 位架构上的效率更高int,甚至可能更高效。longJava 不会分配更少的内存(因为所有东西都必须在字边界上对齐),操作也不会更快(CPU 已经在 64 位块中工作)。