背景
最近在做一个Java的随机对象生成器。需要随机产生一些在[0, n)范围内的short值。
坑
很多人可能会说,这有什么难的,我刚学编程的时候就会了。然后给出下面代码。
short value = (short) (new Random().nextInt(n));
看起来很棒的一段的代码,感觉完全没有问题,但是在实际测试过程中我们发现最后获取到的value值有时候会是负值。咦,怎么回事呢,nextInt(n)返回的不是[0, n)的值吗?
爬坑
为了解释以上问题,我们首先需要回顾下学校学的数值的表示。原码用第一位表示正负,后面的位数表示符号,正数反码,补码与原码都相同,负数除符号位外取反获得反码,反码加一获得补码。
int型在Java中占四个字节,short型在Java中占两个字节,int到short的强转将int的后两个字节截取,比如原本是0x0FFFFFFF,我们知道这是正数,但是如果截断获取后两个字节就编程了0xFFFF,这就是一个负数了。
那么判断一下如果是负数,取反行不行:
short value = (short) (new Random().nextInt(n));
value = (short)(value < 0 ? -value : value);
答案是不行,因为在Java中四则运算的最小类型是int型,short型和byte型的四则运算都是先将值转为Int型在进行的。所以value(0xFFFF -> 0x0000FFFF) < 0, NO. 即使这里取了负值,但是在经过又一次short强转之后又回到了原来的结果。
解决
那么如何解决这一问题呢,下面给出两种正确的解决代码。
n = n < Short.MAX_VALUE ? n : Short.MAX_VALUE;
short value = (short) (new Random().nextInt(n));
因为产生的int值在short值的范围内,所以即使强转也不会改变正负性。因为最大值是0x00007FFF,强转为short,最高位总是0;
short value = (short) (new Random().nextInt(n)) & 0x7FFF;
做位操作,只取产生值的后15位。第16位总是0,所以可以保证是正数。
当然只有在n > Short.MAX_VALUE的情况下才会有问题,如果本省n <= Short.MAX_VALUE,那么也不会有问题。byte与short同理。