谜题1:奇数性
下面判断一个数是否是奇数,这个程序对吗?public static boolean isOdd(int i){
return i % 2 == 1;
}
这里需要注意的是负奇数,负奇数i%2 = -1;
正确的程序可以如下:
public static boolean isOdd(int i){
return i % 2 != 0;
}
或者有性能更好的写法:
public static boolean isOdd(int i){
return (i & 1) != 0;
}
这个怎么去理解它呢?1的二进制000...001, 任何奇数的二进制尾数都是1,任何偶数的二进制尾数都是0;所以奇数i&1的结果!=0;
谜题2:找零时刻
Tom去汽配店买一个1.10元的火花塞,他给店主2元,请问找零多少?public class Change{
public static void main(String args[]){
System.out.println(2.00 - 1.10);
}
}
不是你期望的0.9, 而是0.8999999999999.
问题的根本原因在于二进制不能精确地表示浮点数,可以用BigDecimal来修正一下:
import java.math.BigDecimal;
public class Change1{
public static void main(String args[]){
//此程序我还发现BigDecimal有一个有用的地方,即可以控制打印精度。试着把2.00改成2.0000;
System.out.println(new BigDecimal("2.00").
subtract(new BigDecimal("1.10")));
}
}
但是由于Java没有在语言级别上提供BigDecimal的支持,所以计算是比较慢的。总之,在精确计算的地方避免使用float和double。对于货币的计算,应使用int,long和BigDecimal。
谜题3:长整除
看一眼下面的程序,然后说出结果:public class LongDivision{
public static void main(String args[]){
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);
}
}
上下两个乘法计算表达式对的很整齐,而且使用了long,不会溢出。你几乎都忍不住想说1000,但是你错了。
问题的原因在于参与乘法运算的数字都是int,运算的结果也是int,int就真的会溢出了。
谜题4:初级问题
下面程序的结果是什么?public class Elementary{
public static void main(String[] args){
System.out.println(12345+5432l);
}
}
66666?不对, 是17777。
问题在于5432l,最后的l是L的小写。这个问题很幼稚,但它给了一个深刻的教训,当表示long的时候,一定要用L。同样要避免用L小写l做变量名:
List l = new ArrayList<String>();
l.add("Foo");
System.out.println(1);
谜题5:十六进制的趣事
下面程序的结果是什么?public class JoyOfHex{
public static void main(String[] args){
System.out.println(
Long.toHexString(0x100000000L + 0xcafebabe));
}
}
你可能期待他打印出1cafebabe, 实际上结果却是cafebabe。
问题出在0xcafebabe。我们常见的十进制的字面量可以在前面加符号来表明它是负的。而16进制或8进制,只能看最后二进制形式的最高位了。而0xcafebabe是一个负数。具体的计算过程我就不说了。
谜题6:多重转型
下面多重转型的结果是什么?public class Multicast{
public static void main (String[] args){
System.out.println((int)(char)(byte) -1);
}
}
是65535.
为什么不是-1呢?从较宽整型变成较窄整型,直接砍掉多余的高位;从较窄整型转较宽整型有一点麻烦,不过有一条很简单的规则能够描述从较窄的整型转换成较宽的整型时的符号扩展行为:如果最初的数值类型是有符号的,那么就执行符号扩展;如果它是char,那么不管它将要被转换成什么类型,都执行零扩展。
我们来看这个题,最开始是一个int的-1,它的二进制所有位都是1,它是由符号的,转byte的时候,砍掉高位之后,留下的8位都是1,所以结果仍然是-1,byte转char,执行符号扩展,即把高位用符号位补齐,这里是用1填补高8位;char转int,执行0拓展,即高位补0. 结果变成正的,即2^16-1, 65535.
谜题7:互换内容
下面的程序期望通过一系列的异或来交换两个数。public class CleverSwap{
public static void main(String[] args){
int x = 1984; // (0x7c0)
int y = 2001; // (0x7d1)
x^= y^= x^= y;
System.out.println("x= " + x + "; y= " + y);
}
}
异或的运算大家不熟悉,我只想说一点,不通过中间变量交换两个数的正确做法是:
x^=y; y^=x; x^=y;
谜题8:Dos Equis
public class DosEquis{
public static void main(String[] args){
char x = 'X';
int i = 0;
System.out.println(true ? x : 0);
System.out.println(false ? i : x);
}
}
你心里可能有一个结果,但是估计自己都觉得不靠谱,结果是X88。你知道不靠谱的原因吗?在于条件表达式返回的结果类型的确定,条件表达式返回结果的类型确定有点复杂,我懒得重复,想强调一点就是,条件表达式的第二第三的操作数最好使用同一种类型。
谜题9:半斤
下面的两种写法有什么不一样吗? 请给出x,i的定义,使第一个表达式合法,第二个表达式不合法。x += i;
x = x+1;
问题的关键在于+=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、^=和|=这类复合赋值运算,它含有自动转型。
比如我有:short x = 1; int i = 123456;第一种写法,编译器不会有任何意见(当然结果也会出你意料之外),第二种写法编译器会说不能从int直接转short;
谜题10:八两
和上面的相反,请给出定义,使第一个合法,第二个不合法。x = x + i;
x += i;
Object x = "s"; String i = "i";
复合赋值只能针对简单类型和String。