如下代码:
public class Example027 { public static void main(String[] args) { int i = 0; while (-1 << 32 != 0) { i++; } System.out.println(i); } }
结果说明:
将上述程序放到eclipse中,在输出行会有提示“Unreachable code”。也就是while循环是死循环无法退出。
结果分析:
Java 使用的是基于 2 的补码的二进制算术运算,因此-1 在任何有符号的整数类型中(byte、short、int 或 long)的表示都是所有的位都为1。
常量-1 是所有 32 位都被置位的 int 数值(0xffffffff)。左移操作符将 0 移入到由移位所空出的右边的最低位,因此表达式(-1 << i)将 i 最右边的位设置为 0,并保持其余的 32 - i 位为 1。很明显,这个循环将完成 32 次迭代,因为-1 << i 对任何小于 32 的 i 来说都不等于 0。你可能期望终止条件测试在 i 等于32 时返回 false,从而使程序打印 32,但是它打印的并不是32。 实际上,它不会打印任何东西,而是进入了一个无限循环。
问题在于(-1 << 32)等于-1 而不是 0,因为移位操作符只使用其右操作数的低5 位作为移位长度。或者是低 6 位,如果其左操作数是一个 long 类数值。
这条规则作用于全部的三个移位操作符: <<、 >>和>>>。 移位长度总是介于 0 到31 之间,如果左操作数是 long 类型的,则介于 0 到 63 之间。这个长度是对 32取余的,如果左操作数是 long 类型的,则对 64 取余。如果试图对一个 int 数值移位 32 位,或者是对一个 long 数值移位 64 位,都只能返回这个数值自身的值。没有任何移位长度可以让一个 int 数值丢弃其所有的 32 位,或者是让一个 long 数值丢弃其所有的 64 位。
一种可能的程序改进方案是,我们不是让-1 重复地移位不同的移位长度,而是将前一次移位操作的结果保存起来,并且让它在每一次迭代时都向左再移 1 位。下面这个版本的程序就可以打印出我们所期望的 32:
private static void shift() { int distance = 0; for (int val = -1; val != 0; val <<= 1)//左移32次后,var == 0 distance++; System.out.println(distance); }
很多程序员都希望具有负的移位长度的右移操作符可以起到左移操作符的作用,反之亦然。但是情
况并非如此。右移操作符总是起到右移的作用,而左移操作符也总是起到左移的作用。负的移位长度通过只保留低 5 位而剔除其他位的方式被转换成了正的移位长度(如果左操作数是 long 类型的,则保留低 6 位)。因此,如果要将一个 int数值左移,其移位长度为-1,那么移位的效果是它被左移了31位。
移位长度是对 32 取余的,或者如果左操作数是 long 类型的, 则对 64 取余。因此,使用任何移位操作符和移位长度,都不可能将一个数值的所有位全部移走。同时,我们也不可能用右移操作符来执行左移操作,反之亦然。如果可能的话,请使用常量的移位长度,如果移位长度不能设为常量,那么就要千万当心。
(注:本【java解惑】系列,均是博主阅读《java解惑》原书后,将原书上的讲解和例子部分改编,然后写成博文进行发布的。所有例子均亲自测试通过,并共享在github上。通过这些例子,激励自己,惠及他人。同时,本系列所有博文会同步发布在博主个人微信公众号(搜索“爱题猿”或者“ape_it”),方便大家阅读。如果文中有任何侵犯原作者权利的内容,请及时告知博主,以便及时删除;如果读者对文中的内容有异议或者问题,欢迎通过博客留言或者微信公众号留言等方式共同探讨。)
源代码地址:https://github.com/rocwinger/java-disabuse
本文出自 “winger” 博客,谢绝转载!
原文地址:http://imu2008.blog.51cto.com/3844842/1598768