谜题24:尽情享受每一个字节
public class BigDelight {
public static void main(String[] args) {
for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
if (b == 0x90)
System.out.print("Joy!");
}
}
}
这个程序有两个问题:
问题1 0x90超出了byte的范围;
问题2 0x90是int型,byte与int进行比较的时候,将强行把byte转int再做比较. byte转int执行有符号拓展;
所以上面的程序什么也不会打印出来。
可以思考一下这个比较运算:((byte)0x90 == 0x90)
谜题25:无情的增量操作
public class Increment {
public static void main(String[] args) {
int j = 0;
for (int i = 0; i < 100; i++)
j = j++;
System.out.println(j);
}
}
这个程序的问题就比较明显了,应该看一眼就发现它的问题所在:j = j++;。
谜题26:在循环中
public class InTheLoop {
public static final int END = Integer.MAX_VALUE;
public static final int START = END - 100;
public static void main(String[] args) {
int count = 0;
for (int i = START; i <= END; i++)
count++;
System.out.println(count);
}
}
乍一看,程序会打印出100,细想,(int i = START; i <= END; i++)会执行101次,所以结果应该是101,但是它是个死循环。
当i==Integer.MAX_VALUE这一次循环结束时,i自加1,这个时候i溢出,为-2147483648.
谜题27:变幻莫测的i值
public class Shifty {
public static void main(String[] args) {
int i = 0;
//-1左移i位
while (-1 << i != 0)
i++;
System.out.println(i);
}
}
如果你知道-1的二进制所有的位都是1,你又知道左移位的结果是在移位后,右边空出的位用0补齐, 那么你可能会说这个结果是32. 实际上结果是它进入了死循环.
问题的关键在于移位操作的移位数,即移位操作的右操作数只能是对32取模(如果左操作数是int),或64取模(如果左操作数是long). JLS上面说移位操作只会拿右操作数的低5位(,如果最大为31),或低6位(最大为63)来计算实际要移动的位数.
谜题28:循环者
定义一个start和i,是下面的两个循环成为死循环for (int i = start; i <= start + 1; i++) {}
While (i == i + 1) {}
如果你看过了谜题26,则第一个你应该很容易想出来, int start = Integer.MAX_VALUE - 1;
第二个恐怕没那么好想. 要记住的是, 一个很大的double或float,加上一个很小的数,是没有变化的. 浮点类型的数字越大,它和它相邻数字之间的距离越大, 以至于+1之后不足以弥补这个距离. 这个距离叫做ULP, java里面Math.ulp可以计算这个距离.
这个ulp怎么产生的呢?这就要问浮点在内存中怎么存放的了.比如float类型,在java里面是4byte的32位, 实际在内存中存放的时候,它用一个位存放符号, 用8个位表示阶码, 用23个位表示尾数, 组合起来就像科学记数法那样, 这种表示法下, 产生缝隙就显而易见了.
谜题29:循环者的新娘
请提供一个对i的声明,将下面的循环转变为一个无限循环:while (i != i) {
}
如果你记得NaN, 就是它了. NaN是个很奇怪的常量, 它都不等于它自己.
谜题30:循环者的爱子
请提供一个对i的声明,将下面的循环转变为一个无限循环:while (i != i + 0) {
}
这个应该好想把? String i = "i";
谜题31:循环者的鬼魂
请提供一个对i的声明,将下面的循环转变为一个无限循环:while (i != 0) {
i >>>= 1;
}
如果你定义long i = -1; 然后想像一下, 所有的位都是1, 然后每右移一位,就朝左面加一个0; 最后一共移动了64次就结束了. 怎么才能死循环呢?
如果我定义short i = -1; 会怎么样? >>>=是复合赋值运算(它有隐含的转型), 每朝右移动之前, 要把i转成int, 则执行了有符号拓展, 即前面 高位1补齐; 然后右移一位, 最高位补0, 然后把结果再转成short, 即高16位砍掉. 于是永远也得不到0.
谜题32:循环者的诅咒
请提供一个对i和j的声明,将下面的循环转变为一个无限循环:while (i <= j && j <= i && i != j) {
}
其实这个题还是值得思考的, 至少应该能超前多都一步, 即i<=j&&j<=i这个条件, 它有一个暗示, 就是i和j的内在值相等, 而后面的i!=j可以理解为非同一性.
Integer i = new Integer(0); Integer j = new Integer(0); 即可. 但是Integer i = 0; Integer j = 0;是不行的. 因为Integer在构造的时候, 对于字面量的形式, 会使用缓冲池(它缓冲了-128~127的Integer)里面的Integer实例, 即
Integer i = 1;Integer j = 1;它们具有同一性. 当然如果你知道了这点, 则你也能理解Integer i = 128;Integer j = 128; 也是能让我们这个命题成立的.
谜题33:循环者遇到了狼人
再看看这个:while (i != 0 && i == -i) {
}
这个题很难想的到. Integer.MIN_VALUE可以满足,它的值为-2^32, 取反之后, 本应该等于2^32, 但是2^32已经大于Integer.MAX_VALUE, 即溢出了, 然后又变成-2^32.
谜题34:被计数击倒了
下面程序的结果是什么?public class Count {
public static void main(String[] args) {
final int START = 2000000000;
int count = 0;
for (float f = START; f < START + 50; f++)
count++;
System.out.println(count);
}
}
这个题还是考察可大的浮点数和一个较小的数相加的问题. 如果float f1 = 2000000000;float f2 = 2000000050; 则f1==f2.
本题输出0
谜题35:一分钟又一分钟
下面程序会打印出什么呢?public class Clock {
public static void main(String[] args) {
int minutes = 0;
for (int ms = 0; ms < 60*60*1000; ms++)
if (ms % 60*1000 == 0)
minutes++;
System.out.println(minutes);
}
}
这个题考察了求值顺序问题, ms % 60*1000 == 0 等同于ms % 60 == 0. 所以结果不是60, 而是60000.