一、奇数表达问题
问题:我们经常会认为i%2==1能够判断是否为奇数。
分析:
(1)取模操作符(%):当取余操作返回一个非零的结果时,这个结果一定和左操作符有相同的符号。
举例:
- 3%2=1;
- -3%2=-1; 对应前面的定义,由于左操作符是-3,因此结果应该也是负数。
(2)初步结果:通过i%2==0来判断是否为奇数。
(3)位运算:位运算比一般的算术运算速度快的多。一般偶数的二进制最低位都是0,不管是用原码、补码。
最终结果:(i&1)!=0
例程:
import java.util.Scanner;
public class PuzzleDemo01{
public static void main(String args[]){
Scanner in = new Scanner(System.in);
System.out.print("输入一个整数:");
int a = in.nextInt();
System.out.println("");
System.out.println("i%2==1 -->"+isOdd1(a));
long begin1 = System.nanoTime();
System.out.println("i%2!=0 -->"+isOdd2(a));
long begin2 = System.nanoTime();
System.out.println("i&1!=0 -->"+isOdd3(a));
long begin3 = System.nanoTime();
System.out.println("isOdd2的运算时间:"+(begin2-begin1)+" nano");
System.out.println("isOdd3的运算时间:"+(begin3-begin2)+" nano");
}
public static boolean isOdd1(int a){
return a%2==1;
}
public static boolean isOdd2(int a){
return a%2!=0;
}
public static boolean isOdd3(int a){
return (a&1)!=0;
}
}
/*
测试结果:
输入一个整数:-3
i%2==1 -->false
i%2!=0 -->true
i&1!=0 -->true
isOdd2的运算时间:378819 nano
isOdd3的运算时间:368762 nano
*/
二、二进制浮点数运算的精度问题
问题:2.00-1.10的结果是什么?
分析:
(1)看起来好像很明确是0.90,但是实际上输出的是0.899999。原因:1.10不能被精确的表示成一个double。
(2)因此double和float不适用于精确计算。
(3)java.math.BigDecimal用于精确计算。
(4)用BigDecimal(String)的构造器,而不要用BigDecimal(double)的构造器,因为后者有不可预知性。
最终结果:
new BigDecimal("2.00").subtract(new BigDecimal("1.10"));
import java.math.BigDecimal;
public class PuzzleDemo02{
public static void main(String args[]){
System.out.println("*******一般的浮点数运算:*******");
System.out.println(2.00-1.10); //0.8999999999999999
BigDecimal d1 = new BigDecimal("2.00");
BigDecimal d2 = new BigDecimal("1.10");
BigDecimal result = d1.subtract(d2);
System.out.println("*******运用全精度后:********");
System.out.println(result); //0.90
}
}
三、默认类型问题
问题:long a = 24*60*60*1000*1000;的结果是多少?
分析:
(1)默认来说一个整数的类型就是int,如果是long的话要加L。
(2)以上的运算会先是int的乘法,乘完的结果再转换成long类型,这并不能避免溢出。
(3)int是4字节,因此能表示-231-1 ~231,而上面的运算远远超出了这个范围,因此溢出。
最终结果:
long a = 24L*60*60*1000*1000;
public class PuzzleDemo03{
public static void main(String args[]){
long MICROS_PER_DAY_ORIGINAL = 24 * 60 * 60 * 1000 * 1000;
long MILLIS_PER_DAY_ORIGINAL = 24 * 60 * 60 * 1000;
System.out.println("最初的结果:"
+MICROS_PER_DAY_ORIGINAL/MILLIS_PER_DAY_ORIGINAL);//5
long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
System.out.println("改正后的结果:"
+MICROS_PER_DAY/MILLIS_PER_DAY); //1000
System.out.println("溢出的结果 --> 24 * 60 * 60 * 1000 * 1000="+MICROS_PER_DAY_ORIGINAL); //500654080
System.out.println("不溢出的结果 --> 24L * 60 * 60 * 1000 * 1000="+MICROS_PER_DAY); //86400000000
}
}
四、长整数的表示问题 l还是L?
因为在一般的1和l是差不多的,比如:
最终结果:
(1)在表示long类型的数时要用L.
(2)在表示变量时不能光用 l(小写的L) 表示.
public class PuzzleDemo04{
public static void main(String args[]){
System.out.println("12345+5432l="+(12345+5432l));
System.out.println("12345+54321="+(12345+54321));
System.out.println("12345+5432L="+(12345+5432L));
}
}
/*
12345+5432l=17777
12345+54321=66666
12345+5432L=17777
*/
五、符号扩展和零扩展
问题1:long a = 0xcafebabe;中a的十六进制是多少呢?
分析:
(1)对于有符号数进行扩展时,就需要进行符号扩展。而对于无符号数(char)则采用零扩展。
(2)十六进制和八进制的int型时当最高位是1时,就是负数,符号扩展则高位填1.
最终结果:
结果是0xffffffffcafebabe而不是0x00000000cafebabe;
public class PuzzleDemo05{
public static void main(String args[]){
int a = 0xcafebabe;
long b = a;
System.out.println(a + " --> " + Integer.toHexString(a));
System.out.println(b + " --> " + Long.toHexString(b));
long c = 0x100000000L;
System.out.println("0x100000000L+0xcafebabe="+Long.toHexString(c+b));
long d = 0xcafebabeL;
System.out.println("0x100000000L+0xcafebabeL="+Long.toHexString(c+d));
}
}
/*
-889275714 --> cafebabe
-889275714 --> ffffffffcafebabe
0x100000000L+0xcafebabe=cafebabe
0x100000000L+0xcafebabeL=1cafebabe
*/
问题2:int a = (int)(char)-1;的结果是多少?
分析:
(1)-1是int类型的,转换成char,只需要截断即可,得0xffff.
(2)因为char是无符号的,因此零扩展,则a=0x0000ffff,即65535.
最终结果:
a=65535.
public class PuzzleDemo06{
public static void main(String args[]){
System.out.println((int)(char)(byte)-1);
int i = -1;
char c = (char)i;
int j = c & 0xffff; //无符号扩展
int j2 = c; //无符号扩展
int k = (short)c; //有符号扩展
System.out.println("无符号扩展1:"+j);
System.out.println("无符号扩展2:"+j2);
System.out.println("有符号扩展1:"+k);
}
}
总结:
(1)我们对于类型转换的扩展问题需要注释好意义。
(2)对于无符号扩展也可以通过位掩码来进行完成。
比如:
short s = -1;
int i = s&0xffff; //由于0xffff是int型的,因此s先进行符号扩展成int型,然后进行与,即把高位清除。
六、条件表达式的规则?:
问题:
int i =0;
true? 'x':i;的结果是多少?
A?B:C
1.如果B和C具有相同的类型,则返回的就是B和C的类型。
2.如果一个操作数的类型是T(byte,int,char等),另一个是一个常量表达式,则返回的类型就是T。
3.否则对操作数类型进行二进制数字的提升,返回的类型就是B和C被二进制提升后的类型。
例如:
int i =0;
true? 'x':i;这个由定理2,可以得出结果是int类型,因此结果是‘x’的ASCII码:120;
结论:尽量将B和C的类型相同。
public class PuzzleDemo08{
public static void main(String args[]){
char x = 'X';
int i = 0;
System.out.println(true ? x : 0); //x是char类型,0是常量,因此返回char类型
System.out.println(false? i : x); //i是int变量,'x'是常量,因此返回int
System.out.println(true? 'x': i); //i是int变量,'x'是常量,因此返回int
final int j = 1;
System.out.println(true?x:j); //x是char类型,而j是常量,因此返回char类型
}
}
?:在jdk1.4和jdk1.5的区别:
在1.4中,当第二个操作数和第三个操作数都是引用类型时,条件操作符要求一个必须是另一个的子类型。
5.0中,第二第三个操作数是引用类型时,返回的结果是两类型的最小公共超类。
七、x+=i和x=x+i相等?不!
x+=i等价于x=(Typex)(x+i) 从中看出,中间还有一个隐式的类型转换。
如果 x是short类型,i是int类型,则x+=i 等价于 x=(short)(x+i);
而x=x+i;则没有这个隐式转换,因此如果x的类型的宽度比i的类型的宽度小,则会发生x=x+i的编译错误,而x+=i不会有错。
public class PuzzleDemo09{
public static void main(String args[]){
short x=0;
int i=123456;
x += i; //合法
System.out.println(x);
x=0;
x = (short)(x + i); //x=x+i不合法
System.out.println(x);
}
}
//x+=i 等价于 x=(Typex)(x+i);
但是复合的赋值操作符有一个限制:
(1)左操作数和右操作数都必须是基本数据类型或包装类
(2)如果左操作数的类型是String,则右操作数没有限制。
(3)左操作数不能是其他引用数据类型。
而x=x+i却没有这个限制。
public class PuzzleDemo10{
public static void main(String args[]){
String i = "abc";
Object x = "cba";
//x+=i; 不合法
x=x+i; //合法
System.out.println(x);
}
}