谜题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!");
}
}
}
题目: 如果你运行该程序,就会发现它没有打印任何东西
解决方案:
你可以将int转型为byte,之后你就可以拿一个byte与另一个byte进行比较了:
if (b == (byte)0x90)
System.out.println("Joy!");
或者,你可以用一个屏蔽码来消除符号扩展的影响,从而将byte转型为int,之后你就可以拿一个int与另一个int进行比较了:
if ((b & 0xff) == 0x90)
System.out.print("Joy!");
谜题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);
}
}
题目: 输出0
原因:当++操作符被置于一个变量值之后时,其作用就是一个后缀增量操作符
(postfix increment operator)[JLS 15.14.2]: 表达式j++的值等于j在执
行增量操作之前的初始值。因此,前面提到的赋值语句首先保存j的值,然后将j设置为其值加1,
最后将j复位到它的初始值。换句话说,这个赋值操作等价于下面的语句序列:
int tmp = j;
j = j + 1;
j = tmp?;
谜题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);
}
}
题目: 无限循环
原因:问题在于这个循环会在循环索引(i)小于或等于Integer.MAX_VALUE时持续运行,
但是所有的int变量都是小于或等于Integer.MAX_VALUE的。因为它被定义为所有int数值中的最大值。
当i达到Integer.MAX_VALUE,并且再次被执行增量操作时,它就有绕回到了Integer.MIN_VALUE。
如果你需要的循环会迭代到int数值的边界附近时,你最好是使用一个long变量作为循环索引。只需
将循环索引的类型从int改变为long就可以解决该问题,从而使程序打印出我们所期望的101:
谜题27:变幻莫测的i值
public class Shifty {
public static void main(String[] args) {
int i = 0;
while (-1 << i != 0)
i++;
System.out.println(i);
}
}
题目:
原因:问题在于(-1 << 32)等于-1而不是0,因为移位操作符之使用其右操作数的低5位作为移位长度。
或者是低6位,如果其左操作数是一个long类数值[JLS 15.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);
}
}
谜题28:循环者
什么样的声明能够让下面的循环变成一个无限循环?
While (i == i + 1) {}
题目:Double i = Double.POSITIVE_INFINITY;
System.out.println(i == i+1);
谜题29:循环者的新娘
请提供一个对i的声明,将下面的循环转变为一个无限循环:
while (i != i) {
}
题目:
原因:IEEE 754浮点算术保留了一个特殊的值用来表示一个不是数字的数量[IEEE754]。
这个值就是NaN(“不是一个数字(Not a Number)”的缩写),对于所有没有良
好的数字定义的浮点计算,例如0.0/0.0,其值都是它。规范中描述道,NaN不等
于任何浮点数值,包括它自身在内[JLS 15.21.1]。因此,如果i在循环开始之前被初
始化为NaN,那么终止条件测试(i != i)的计算结果就是true,循环就永远不会终止。
很奇怪但却是事实。
谜题30:循环者的爱子
请提供一个对i的声明,将下面的循环转变为一个无限循环:
while (i != i + 0) {
}
与前一个谜题不同,你必须在你的答案中不使用浮点数。换句话说,你不能把i声明为double或float类型的.
题目: 使用Sring
谜题31:循环者的鬼魂
请提供一个对i的声明,将下面的循环转变为一个无限循环:
while (i != 0) {
i >>>= 1;
}
题目: short i = -1;
因为i的初始值((short)0xffff)是非0的,所以循环体会被执行。在执行移位操作时,第一步是将i提升为int类型。所有算数操作都会对short、byte和char类型的操作数执行这样的提升。这种提升是一个拓宽原始类型转换,因此没有任何信息会丢失。这种提升执行的是符号扩展,因此所产生的int数值是0xffffffff。然后,这个数值右移1位,但不使用符号扩展,因此产生了int数值0x7fffffff。最后,这个数值被存回到i中。为了将int数值存入short变量,Java执行的是可怕的窄化原始类型转换,它直接将高16位截掉。这样就只剩下(short)oxffff了,我们又回到了开始处。循环的第二次以及后续的迭代行为都是一样的,因此循环将永远不会终止。
如果你将i声明为一个short或byte变量,并且初始化为任何负数,那么这种行为也会发生。如果你声明i为一个char,那么你将无法得到无限循环,因为char是无符号的,所以发生在移位之前的拓宽原始类型转换不会执行符号扩展。
总之,不要在short、byte或char类型的变量之上使用复合赋值操作符。因为这样的表达式执行的是混合类型算术运算,它容易造成混乱。更糟的是,它们执行将隐式地执行会丢失信息的窄化转型,其结果是灾难性的。
谜题32:循环者的诅咒
请提供一个对i的声明,将下面的循环转变为一个无限循环:
while (i <= j && j <= i && i != j) {
}
题目:
Integer i = new Integer(0);
Integer j = new Integer(0);
谜题33:循环者遇到了狼人
请提供一个对i的声明,将下面的循环转变为一个无限循环。这个循环不需要使用任何5.0版的特性:
while (i != 0 && i == -i) {
}
题目:
int i = Integer.MIN_VALUE;
下面这个也可以:
long i = Long.MIN_VALUE;
谜题34:被计数击倒了
与谜题26和27中的程序一样,下面的程序有一个单重的循环,它记录迭代的次数,并在循环终止时打印这个数。那么,这个程序会打印出什么呢?
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类型的,而非int类型的。回想一下谜题28,很明显,增量操作(f++)不能正常工作。F的初始值接近于Integer.MAX_VALUE,因此它需要用31位来精确表示,而float类型只能提供24位的精度。对如此巨大的一个float数值进行增量操作将不会改变其值。因此,这个程序看起来应该无限地循环下去,因为f永远也不可能解决其终止值。但是,如果你运行该程序,就会发现它并没有无限循环下去,事实上,它立即就终止了,并打印出0。
谜题35:一分钟又一分钟
下面的程序在模仿一个简单的时钟。它的循环变量表示一个毫秒计数器,其计数值从0开始直至一小时中包含的毫秒数。循环体以定期的时间间隔对一个分钟计数器执行增量操作。最后,该程序将打印分钟计数器。那么它会打印出什么呢?
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);
}
修正:
if (ms % (60 * 1000) == 0)
minutes++;