您的位置 首页 java

你不知道的Java——22.移位长度

下面的程序使用的是左移操作符(<<),包含了一个记录在终止前有多少次迭代的循环。那么下面这个程序将会打印出什么来呢?

友情提醒:当你阅读这个程序时,请记住 Java 使用的是基于 2 的补码的 二进制 算术运算,因此-1 在任何有符号的整数类型中(byte、short、int 或 long)的表示都是所有的位被置位:

public class Shifty {

public static void main(String[] args) {

int i = 0;

while (-1 << i != 0)

i++;

System.out.println(i);

}

}

常量 -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 类数值[JLS15.19]。 这条规则作用于全部的三个移位操作符:<<、>>和>>>。移位长度总是介于 0 到31 之间,如果左 操作数 是 long 类型的,则介于 0 到 63 之间。

这个长度是对 32取余的,如果左操作数是 long 类型的,则对 64 取余。如果试图对一个 int 数值移位 32 位,或者是对一个 long 数值移位 64 位,都只能返回这个数值自身的值。

没有任何移位长度可以让一个 int 数值丢弃其所有的 32 位,或者是让一个 long 数值丢弃其所有的 64 位。

不过幸运的是,有一个非常容易的方法能够订正该问题。我们不是让-1 重复地移位不同的移位长度,而是将前一次移位操作的结果保存起来,并且让它在每一次迭代时都向左再移 1 位。下面这个版本的程序就可以打印出我们所期望的 32:

public class Shifty {

public static void main(String[] args) {

int distance = 0;

for (int val = -1; val != 0; val <<= 1)

distance++;

System.out.println(distance);

}

}

这个订正过的程序说明了一条普遍的原则: 如果可能的话,移位长度应该是常量。

如果移位长度紧盯着你不放,那么你让其值超过 31,或者如果左操作数是 long 类型的,让其值超过 63 的可能性就会大大降低。

当然,你并不可能总是可以使用常量的移位长度。当你必须使用一个非常量的移位长度时,请确保你的程序可以应付这种容易产生问题的情况,或者压根就不会碰到这种情况。

前面提到的移位操作符的行为还有另外一个令人震惊的结果。

很多程序员都希望具有负的移位长度的右移操作符可以起到左移操作符的作用,反之亦然。

但是情况并非如此。右移操作符总是起到右移的作用,而左移操作符也总是起到左移的作用。负的移位长度通过只保留低 5 位而剔除其他位的方式被转换成了正的移位长度——如果左操作数是 long 类型的,则保留低 6 位。

因此,如果要将一个 int数值左移,其移位长度为-1,那么移位的效果是它被左移了 31 位。

总之,移位长度是对 32 取余的,或者如果左操作数是 long 类型的,则对 64 取余。

因此,使用任何移位操作符和移位长度,都不可能将一个数值的所有位全部移走。同时,我们也不可能用右移操作符来执行左移操作,反之亦然。

如果可能的话,请使用常量的移位长度,如果移位长度不能设为常量,那么就要千万当心。

当我们在设计程序时应该考虑将移位长度限制在从 0 到以位为单位的类型尺寸的范围内,并且修改移位长度为类型尺寸时的语义,让其返回 0。

尽管这可以避免在本次问题中所展示的混乱情况,但是它可能会带来负面的执行结果,因为 Java 的移位操作符的语义正是许多处理器上的移位指令的语义。

文章来源:智云一二三科技

文章标题:你不知道的Java——22.移位长度

文章地址:https://www.zhihuclub.com/195549.shtml

关于作者: 智云科技

热门文章

网站地图