`

[转]java解惑你知多少 1

    博客分类:
  • JAVA
阅读更多

数值表达式
1. 奇偶判断
不要使用 i % 2 == 1 来判断是否是奇数,因为i为负奇数时不成立,请使用 i % 2 != 0 来判断是否是奇数,或使用

高效式 (i & 1) != 0来判断。

 

2. 小数精确计算
Java代码 
1.System.out.println(2.00 -1.10);//0.8999999999999999  
System.out.println(2.00 -1.10);//0.8999999999999999 
上面的计算出的结果不是 0.9,而是一连串的小数。问题在于1.1这个数字不能被精确表示为一个double,因此它被表

示为最接近它的double值,该程序从2中减去的就是这个值,但这个计算的结果并不是最接近0.9的double值。


一般地说,问题在于并不是所有的小数都可以用二进制浮点数精确表示。


二进制浮点对于货币计算是非常不适合的,因为它不可能将1.0表示成10的其他任何负次幂。

 

解决问题的第一种方式是使用货币的最小单位(分)来表示:

Java代码 
1.System.out.println(200-110);//90  
System.out.println(200-110);//90

第二种方式是使用BigDecimal,但一定要用BigDecimal(String)构造器,而千万不要用BigDecimal(double)来构造(

也不能将float或double型转换成String再来使用BigDecimal(String)来构造,因为在将float或double转换成String

时精度已丢失)。例如new BigDecimal(0.1),它将返回一个BigDecimal,也即

0.1000000000000000055511151231257827021181583404541015625,正确使用BigDecimal,程序就可以打印出我们所期

望的结果0.9:

Java代码 
1.System.out.println(new BigDecimal("2.0").subtract(new BigDecimal("1.10")));// 0.9  
System.out.println(new BigDecimal("2.0").subtract(new BigDecimal("1.10")));// 0.9

另外,如果要比较两个浮点数的大小,要使用BigDecimal的compareTo方法。

 

如果你还想更深入了解下,请参考《Java中的浮点数剖析》!

3. int整数相乘溢出
我们计算一天中的微秒数:

Java代码 
1.long microsPerDay = 24 * 60 * 60 * 1000 * 1000;// 正确结果应为:86400000000   
2.System.out.println(microsPerDay);// 实际上为:500654080  
long microsPerDay = 24 * 60 * 60 * 1000 * 1000;// 正确结果应为:86400000000
System.out.println(microsPerDay);// 实际上为:500654080问题在于计算过程中溢出了。这个计算式完全是以int运算来执行的,并且只有在运算完成之后,其结果才被提升为

long,而此时已经太迟:计算已经溢出。

 

解决方法使计算表达式的第一个因子明确为long型,这样可以强制表达式中所有的后续计算都用long运算来完成,这

样结果就不会溢出:

Java代码 
1.long microsPerDay = 24L * 60 * 60 * 1000 * 1000;  
long microsPerDay = 24L * 60 * 60 * 1000 * 1000;

4. 负的十六进制与八进制字面常量
“数字字面常量”的类型都是int型,而不管他们是几进制,所以“2147483648”、“0x180000000(十六进制,共33

位,所以超过了整数的取值范围)”字面常量是错误的,编译时会报超过int的取值范围了,所以要确定以long来表示

“2147483648L”、“0x180000000L”。

 

十进制字面常量只有一个特性,即所有的十进制字面常量都是正数,如果想写一个负的十进制,则需要在正的十进制

字面常量前加上“-”即可。

 

十六进制或八进制字面常量可就不一定是正数或负数,是正还是负,则要根据当前情况看:如果十六进制和八进制字

面常量的最高位被设置成了1,那么它们就是负数:

Java代码 
1.System.out.println(0x80);//128    
2.//0x81看作是int型,最高位(第32位)为0,所以是正数   
3.System.out.println(0x81);//129    
4.System.out.println(0x8001);//32769   
5.System.out.println(0x70000001);//1879048193    
6.//字面量0x80000001为int型,最高位(第32位)为1,所以是负数   
7.System.out.println(0x80000001);//-2147483647   
8.//字面量0x80000001L强制转为long型,最高位(第64位)为0,所以是正数   
9.System.out.println(0x80000001L);//2147483649   
10.//最小int型   
11.System.out.println(0x80000000);//-2147483648   
12.//只要超过32位,就需要在字面常量后加L强转long,否则编译时出错   
13.System.out.println(0x8000000000000000L);//-9223372036854775808  
System.out.println(0x80);//128 
//0x81看作是int型,最高位(第32位)为0,所以是正数
System.out.println(0x81);//129 
System.out.println(0x8001);//32769
System.out.println(0x70000001);//1879048193 
//字面量0x80000001为int型,最高位(第32位)为1,所以是负数
System.out.println(0x80000001);//-2147483647
//字面量0x80000001L强制转为long型,最高位(第64位)为0,所以是正数
System.out.println(0x80000001L);//2147483649
//最小int型
System.out.println(0x80000000);//-2147483648
//只要超过32位,就需要在字面常量后加L强转long,否则编译时出错
System.out.println(0x8000000000000000L);//-9223372036854775808从上面可以看出,十六进制的字面常量表示的是int型,如果超过32位,则需要在后面加“L”,否则编译过不过。如

果为32,则为负int正数,超过32位,则为long型,但需明确指定为long。

 

Java代码 
1.System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));// cafebabe  
System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));// cafebabe结果为什么不是0x1cafebabe?该程序执行的加法是一个混合类型的计算:左操作数是long型,而右操作数是int类型

。为了执行该计算,Java将int类型的数值用拓宽原生类型转换提升为long类型,然后对两个long类型数值相加。因为

int是有符号的整数类型,所以这个转换执行的是符号扩展。


这个加法的右操作数0xcafebabe为32位,将被提升为long类型的数值0xffffffffcafebabeL,之后这个数值加上了左操

作0x100000000L。当视为int类型时,经过符号扩展之后的右操作数的高32位是-1,而左操作数的第32位是1,两个数

值相加得到了0:
  0x 0xffffffffcafebabeL
+0x 0000000100000000L
-----------------------------
 0x 00000000cafebabeL

如果要得到正确的结果0x1cafebabe,则需在第二个操作数组后加上“L”明确看作是正的long型即可,此时相加时拓

展符号位就为0:

Java代码 
1.System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));// 1cafebabe  
System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));// 1cafebabe

5. 窄数字类型提升至宽类型时使用符号位扩展还是零扩展
Java代码 
1.System.out.println((int)(char)(byte)-1);// 65535  
System.out.println((int)(char)(byte)-1);// 65535结果为什么是65535而不是-1?

 

窄的整型转换成较宽的整型时符号扩展规则:如果最初的数值类型是有符号的,那么就执行符号扩展(即如果符号位

为1,则扩展为1,如果为零,则扩展为0);如果它是char,那么不管它将要被提升成什么类型,都执行零扩展。

 

了解上面的规则后,我们再来看看迷题:因为byte是有符号的类型,所以在将byte数值-1(二进制为:11111111)提

升到char时,会发生符号位扩展,又符号位为1,所以就补8个1,最后为16个1;然后从char到int的提升时,由于是

char型提升到其他类型,所以采用零扩展而不是符号扩展,结果int数值就成了65535。

 

如果将一个char数值c转型为一个宽度更宽的类型时,只是以零来扩展,但如果清晰表达以零扩展的意图,则可以考虑

使用一个位掩码:

Java代码 
1.int i = c & 0xffff;//实质上等同于:int i = c ;  
int i = c & 0xffff;//实质上等同于:int i = c ;

如果将一个char数值c转型为一个宽度更宽的整型,并且希望有符号扩展,那么就先将char转型为一个short,它与

char上个具有同样的宽度,但是它是有符号的:

Java代码 
1.int i = (short)c;  
int i = (short)c;

如果将一个byte数值b转型为一个char,并且不希望有符号扩展,那么必须使用一个位掩码来限制它:

Java代码 
1.char c = (char)(b & 0xff);// char c = (char) b;为有符号扩展  
char c = (char)(b & 0xff);// char c = (char) b;为有符号扩展 
6. ((byte)0x90 == 0x90)?
答案是不等的,尽管外表看起来是成立的,但是它却等于false。为了比较byte数值(byte)0x90和int数值0x90,Java

通过拓宽原生类型将byte提升为int,然后比较这两个int数值。因为byte是一个有符号类型,所以这个转换执行的是

符号扩展,将负的byte数值提升为了在数字上相等的int值(10010000111111111111111111111111 10010000)。在本例中,该转换将(byte)0x90提升为int数值-112,它不等于int数值的0x90,即+144。


解决办法:使用一个屏蔽码来消除符号扩展的影响,从而将byte转型为int。

Java代码 
1.((byte)0x90 & 0xff)== 0x90  
((byte)0x90 & 0xff)== 0x90

7. 三元表达式(?:)
Java代码 
1.char x = 'X';   
2.int i = 0;   
3.System.out.println(true ? x : 0);// X   
4.System.out.println(false ? i : x);// 88  
char x = 'X';
int i = 0;
System.out.println(true ? x : 0);// X
System.out.println(false ? i : x);// 88 条件表达式结果类型的规则:
(1) 如果第二个和第三个操作数具有相同的类型,那么它就是条件表达式的类型。
(2) 如果一个操作的类型是T,T表示byte、short或char,而另一个操作数是一个int类型的“字面常量”,并且

它的值可以用类型T表示,那条件表达式的类型就是T。
(3) 否则,将对操作数类型进行提升,而条件表达式的类型就是第二个和第三个操作被提升之后的类型。

 

现来使用以上规则解上面的迷题,第一个表达式符合第二条规则:一个操作数的类型是char,另一个的类型是字面常

量为0的int型,但0可以表示成char,所以最终返回类型以char类型为准;第二个表达式符合第三条规则:因为i为int

型变量,而x又为char型变量,所以会先将x提升至int型,所以最后的结果类型为int型,但如果将i定义成final时,

则返回结果类型为char,则此时符合第二条规则,因为final类型的变量在编译时就使用“字面常量0”来替换三元表

达式了:

Java代码 
1.final int i = 0;   
2.System.out.println(false ? i : x);// X  
final int i = 0;
System.out.println(false ? i : x);// X

在JDK1.4版本或之前,条件操作符 ?: 中,当第二个和延续三个操作数是引用类型时,条件操作符要求它们其中一个

必须是另一个的子类型,那怕它们有同一个父类也不行:

Java代码 
1.public class T {   
2. public static void main(String[] args) {   
3.  System.out.println(f());   
4. }   
5. public static T f() {   
6.  // !!1.4不能编译,但1.5可以   
7.  // !!return true?new T1():new T2();   
8.  return true ? (T) new T1() : new T2();// T1   
9. }   
10.}   
11.  
12.class T1 extends T {   
13. public String toString() {   
14.  return "T1";   
15. }   
16.}   
17.  
18.class T2 extends T {   
19. public String toString() {   
20.  return "T2";   
21. }   
22.}  
public class T {
 public static void main(String[] args) {
  System.out.println(f());
 }
 public static T f() {
  // !!1.4不能编译,但1.5可以
  // !!return true?new T1():new T2();
  return true ? (T) new T1() : new T2();// T1
 }
}

class T1 extends T {
 public String toString() {
  return "T1";
 }
}

class T2 extends T {
 public String toString() {
  return "T2";
 }
}

在5.0或以上版本中,条件操作符在延续二个和第三个操作数是引用类型时总是合法的。其结果类型是这两种类型的最

小公共超类。公共超类总是存在的,因为Object是每一个对象类型的超类型,上面的最小公共超类是T,所以能编译。

 

 

 

 

 

 

8. +=复合赋值问题
x+=i与x=x+i等效吗,许多程序员都会认为第一个表达式x+=i只是第二个表达式x=x+i的简写方式,但这并不准确。

 

Java语言规范中提到:复合赋值 E1 op= E2等价于简单赋值 E1 = (T)((E1) op (E2)),其中T是E1的类型。

复合赋值表达式自动地将所执行计算的结果转型为其左侧变量的类型。如果结果的类型与该变量的类型相同,那么这

个转型不会造成任何影响,然而,如果结果的类型比该变量的类型要宽,那么复合赋值操作符将悄悄地执行一个窄化

原生类型转换,这样就会导致结果不正确:

Java代码 
1.short x=0;   
2.int i = 123456;   
3.x +=i;   
4.System.out.println(x);//-7616  
short x=0;
int i = 123456;
x +=i;
System.out.println(x);//-7616

使用简单的赋值方式就不会有这样的问题了,因为宽类型不能自动转换成窄的类型,编译器会报错,这时我们就会注

意到错误:x = x + i;//编译通不过

 

请不要将复合赋值操作符作用于byte、short或char类型的变量;在将复合赋值操作符作用于int类型的变量时,要确

保表达式右侧不是long、float或double类型;在将复合赋值操作符作用于float类型的变量时,要确保表达式右侧不

是double类型。其实一句:不要将让左侧的类型窄于右侧的数字类型。

 

总之,不要在short、byte或char类型的变量之上使用复合赋值操作符,因为这一过程会伴随着计算前类型的提升与计

算后结果的截断,导致最后的计算结果不正确。


9. i =++i;与i=i++;的区别
Java代码 
1.int i = 0;    
2.i = i++;   
3.System.out.println(i);  
int i = 0; 
i = i++;
System.out.println(i);上面的程序会输出什么?大部分会说是 1,是也,非也。运行时正确结果为0。

 

i=++i;相当于以下二个语句(编译时出现警告,与i=i;警告相同):
i=i+1;
i=i;

 

i = i++;相当于以下三个语句:
int tmp = i;
i = i + 1;
i = tmp;

 

下面看看下面程序片段:

Java代码 
1.int i = 0, j = 0, y = 0;   
2.i++;//相当于:i=i+1;   
3.System.out.println("i=" + i);// i=1   
4.++i;//相当于:i=i+1;   
5.System.out.println("i=" + i);// i=2   
6.i = i++;//相当于:int tmp=i;i=i+1;i=tmp;   
7.System.out.println("i=" + i);// i=2   
8.i = ++i;//编译时出现警告,与i=i;警告相同。相当于:i=i+1;i=i;   
9.System.out.println("i=" + i);// i=3   
10.j = i++;//相当于:int tmp=i;i=i+1;j=tmp;   
11.System.out.println("j=" + j);// j=3   
12.System.out.println("i=" + i);// i=4   
13.y = ++i;//相当于:i=i+1;y=i;   
14.System.out.println("y=" + y);// y=5   
15.System.out.println("i=" + i);// i=5  
int i = 0, j = 0, y = 0;
i++;//相当于:i=i+1;
System.out.println("i=" + i);// i=1
++i;//相当于:i=i+1;
System.out.println("i=" + i);// i=2
i = i++;//相当于:int tmp=i;i=i+1;i=tmp;
System.out.println("i=" + i);// i=2
i = ++i;//编译时出现警告,与i=i;警告相同。相当于:i=i+1;i=i;
System.out.println("i=" + i);// i=3
j = i++;//相当于:int tmp=i;i=i+1;j=tmp;
System.out.println("j=" + j);// j=3
System.out.println("i=" + i);// i=4
y = ++i;//相当于:i=i+1;y=i;
System.out.println("y=" + y);// y=5
System.out.println("i=" + i);// i=5 
10. Integer.MAX_VALUE + 1=?
Java代码 
1.System.out.println(Integer.MAX_VALUE + 1);  
System.out.println(Integer.MAX_VALUE + 1);上面的程序输出多少?2147483647+1=2147483648?答案为-2147483648。

 

查看源码Integer.MAX_VALUE 为MAX_VALUE = 0x7fffffff;所以加1后为0x80000000,又0x80000000为整型字面常量,满了32位,且最位为1,所以字面上等于 -0,但又由于 -0就是等于0,所以-0这个编码就规定为最小的负数,32位的

最小负数就是-2147483648。


11. -1<<32=?、-1<<65=?
如果左操作数是int(如果是byte、short、char型时会提升至int型再进行位操作)型,移位操作符只使用其右操作数

的低5位作为移位长度(也就是将右操作数除以32取余);如果左操作数是long型,移位操作符只使用其右操作数的低

6位作为移位长度(也就是将右操作数除以64取余);

 

再看看下面程序片段就会知道结果:

Java代码 
1.System.out.println(-1 << 31);// -2147483648 向左移31%32=31位   
2.System.out.println(-1 << 32);// -1 向左移32%32=0位   
3.System.out.println(-1 << 33);// -2 向左移33%32=1位   
4.System.out.println(-1 << 1);// -2 向左移1%32=1位   
5.  
6.System.out.println(-1L << 63);// -9223372036854775808 向左移63%64=63位   
7.System.out.println(-1L << 64);// -1 向左移64%64=0位   
8.System.out.println(-1L << 65);// -2 向左移65%64=1位   
9.System.out.println(-1L << 1);// -2 向左移1%64=1位   
10.  
11.byte b = -1;// byte型在位操作前类型提升至int   
12.System.out.println(b << 31);// -2147483648 向左移31%32=31位   
13.System.out.println(b << 63);// -2147483648 向左移63%32=31位   
14.  
15.short s = -1;// short型在位操作前类型提升至int   
16.System.out.println(s << 31);// -2147483648 向左移31%32=31位   
17.System.out.println(s << 63);// -2147483648 向左移63%32=31位   
18.  
19.char c = 1;// char型在位操作前类型提升至int   
20.System.out.println(c << 31);// -2147483648 向左移31%32=31位   
21.System.out.println(c << 63);// -2147483648 向左移63%32=31位  
System.out.println(-1 << 31);// -2147483648 向左移31%32=31位
System.out.println(-1 << 32);// -1 向左移32%32=0位
System.out.println(-1 << 33);// -2 向左移33%32=1位
System.out.println(-1 << 1);// -2 向左移1%32=1位

System.out.println(-1L << 63);// -9223372036854775808 向左移63%64=63位
System.out.println(-1L << 64);// -1 向左移64%64=0位
System.out.println(-1L << 65);// -2 向左移65%64=1位
System.out.println(-1L << 1);// -2 向左移1%64=1位

byte b = -1;// byte型在位操作前类型提升至int
System.out.println(b << 31);// -2147483648 向左移31%32=31位
System.out.println(b << 63);// -2147483648 向左移63%32=31位

short s = -1;// short型在位操作前类型提升至int
System.out.println(s << 31);// -2147483648 向左移31%32=31位
System.out.println(s << 63);// -2147483648 向左移63%32=31位

char c = 1;// char型在位操作前类型提升至int
System.out.println(c << 31);// -2147483648 向左移31%32=31位
System.out.println(c << 63);// -2147483648 向左移63%32=31位

12. 一个数永远不会等于它自己加1吗?i==i+1
一个数永远不会等于它自己加1,对吗?如果数字是整型,则对;如果这个数字是无穷大或都是浮点型足够大(如

1.0e40),等式就可能成立了。

 

Java强制要求使用IEEE 754浮点数算术运算,它可以让你用一个double或float来表示无穷大。

 

浮点型分为double型、float型。

 

无穷分为正无穷与负无穷。

 

无穷大加1还是无穷大。

 

一个浮点数值越大,它和其后继数值之间的间隔就越大。

 

对一个足够大的浮点数加1不会改变它的值,因为1不足以“填补它与其后者之间的空隙”。

 

浮点数操作返回的是最接近其精确数学结果的浮点数值。

 

一旦毗邻的浮点数值之间的距离大于2,那么对其中的一个浮点数值加1将不会产生任何效果,因为其结果没有达到两

个数值之间的一半。对于float类型,加1不会产生任何效果的最小数是2^25,即33554432;而对于double类型,最小

数是2^54,大约是1.8*10^16。

 

33554432F转二进制过程:
33554432的二进制为:10000000000000000000000000,将该二进制化成规范的小数二进制,即小数从右向左移25位

1.0000000000000000000000000,化成浮点数二进制0,25+127, 00000000000000000000000 00(丢弃最后两位),即0, 10011000, 00000000000000000000000,最后的结果为1.00000000000000000000000*2^25


毗邻的浮点数值之间的距离被称为一个ulp,它是最小单位(unit in the last place)的首字母缩写。在5.0版本中,引入了Math.ulp方法来计算float或double数值的ulp。

 

二进制浮点算术只是对实际算术的一种近似。

Java代码 
1.// 注,整型数不能被 0 除,即(int)XX/0运行时抛异常   
2.double i = 1.0 / 0.0;// 正无穷大   
3.double j = -1.0 / 0.0;// 负无穷大   
4.// Double.POSITIVE_INFINITY定义为:POSITIVE_INFINITY = 1.0 / 0.0;   
5.System.out.println(i + " " + (i == Double.POSITIVE_INFINITY));//Infinity true   
6.// Double.NEGATIVE_INFINITY定义为:NEGATIVE_INFINITY = -1.0 / 0.0;   
7.System.out.println(j + " " + (j == Double.NEGATIVE_INFINITY));//-Infinity true   
8.System.out.println(i == (i + 1));// true   
9.System.out.println(0.1f == 0.1);// false   
10.float f = 33554432;   
11.System.out.println(f + " " + (f==(f+1)));//3.3554432E7 true  
// 注,整型数不能被 0 除,即(int)XX/0运行时抛异常
double i = 1.0 / 0.0;// 正无穷大
double j = -1.0 / 0.0;// 负无穷大
// Double.POSITIVE_INFINITY定义为:POSITIVE_INFINITY = 1.0 / 0.0;
System.out.println(i + " " + (i == Double.POSITIVE_INFINITY));//Infinity true
// Double.NEGATIVE_INFINITY定义为:NEGATIVE_INFINITY = -1.0 / 0.0;
System.out.println(j + " " + (j == Double.NEGATIVE_INFINITY));//-Infinity true
System.out.println(i == (i + 1));// true
System.out.println(0.1f == 0.1);// false
float f = 33554432;
System.out.println(f + " " + (f==(f+1)));//3.3554432E7 true 
13. 自己不等于自己吗?i!=i
NaN(Not a Number)不等于任何数,包括它自身在内。

 

double i = 0.0/0.0;可表示NaN。

 

float和double类型都有一个特殊的NaN值,Double.NaN、Float.NaN表示NaN。

 

如果一个表达式中产生了NaN,则结果为NaN。

Java代码 
1.System.out.println(0.0 / 0.0);// NaN   
2.System.out.println(Double.NaN + " " + (Double.NaN == (0.0 / 0.0)));//NaN false  
System.out.println(0.0 / 0.0);// NaN
System.out.println(Double.NaN + " " + (Double.NaN == (0.0 / 0.0)));//NaN false

14. 自动拆箱
Java代码 
1.// 為了兼容以前版本,1.5不會自動拆箱   
2.System.out.println(new Integer(0) == new Integer(0));// false   
3.// 1.4编译非法,1.5会自动拆箱   
4.System.out.println(new Integer(0) == 0);// true  
// 為了兼容以前版本,1.5不會自動拆箱
System.out.println(new Integer(0) == new Integer(0));// false
// 1.4编译非法,1.5会自动拆箱
System.out.println(new Integer(0) == 0);// true

15. 为什么-0x00000000==0x00000000、-0x80000000== 0x80000000
为了取一个整数类型的负值,要对其每一位取反(如果是对某个十六进制形式整数求负,如:-0x00000000则直接对这

个十六进制数进行各位取反操作——但不包括前面的负号;如果是对某个十进制求负,如-0,则需先求其绝对值的十

六进制的原码后,再各位取反),然后再加1。


注:如果是对某个十进制数求负,如-1(0xffffffff),实质上按照平时求一个负数补码的方式来处理也是一样的,求某个负数的补码规则为:先求这个数绝对值的原码,然后从该二进制的右边开始向左找第一个为1的位置,最后将这个1前的各位取反(包括最高位符号位,即最高位0取反后为1),其他位不变,最终所得的二进制就为这个负数的补码,也就是最终在内存中负数所表示的形式。不过在找这个第一个为1时可能找不到或在最高位,比如-0,其绝对值为0(0x00000000);也有可能最高位为1,比如-2147483648,其绝对值为2147483648(0x80000000),如果遇到绝对值的原码为0x00000000或0x80000000的情况下则不变,即为绝对值的原码本身。

 

-0x00000000的运算过程:对0x00000000先取反得到0xffffffff,再加1,-0x00000000的最后结果就为 0xffffffff+1

,其最后的结果还是0x00000000,所以-0x00000000 == 0x00000000。前面是对0x00000000求负的过程,如果是对0求负呢?先求0的十六进制形式0x00000000,再按前面的过程来即可。或者根据前面规则对0x00000000求负不变,即最后

结果还是0x00000000。

 

-0x80000000的运算过程:对0x80000000先取反得到0x7fffffff,再加1,-0x80000000的最后结果就为 0x7fffffff+1

,其最后的结果还是0x80000000,即-0x80000000 == 0x80000000。前面是对0x80000000求负的过程,如果是对

2147483648求负呢?先求2147483648的十六进制形式0x80000000,再按前面的过程来即可。或者根据前面规则对0x80000000求负不变,即最后结果还是0x80000000。

 

-0x00000001的运算过程,实质上就是求-1的补码过程,即对其绝对值的十六进制0x00000001求补码,即为0xffffffff

,即-1的补码为0xffffffff。

 

Java代码 
1.System.out.println(Integer.MIN_VALUE == -Integer.MIN_VALUE);// true    
2./*  
3. *  0x80000000取反得0x7fffffff,再加1得0x80000000,因为负数是  
4. *  以补码形式存储于内存中的,所以推导出结果原码为:0x80000000,  
5. *  即为-0,又因为-0是等于0的,所以不需要-0这个编码位,那就多了  
6. *  一个0x80000000编码位了,所以最后就规定0x80000000为最小负数   
7. */  
8.System.out.println(-0x80000000);// -2147483648   
9./*  
10. *  0x7fffffff取反得0x80000000,再加1得0x80000001,因为负数是  
11. *  以补码形式存储于内存中的,所以推导出结果原码为:0xffffffff,  
12.*  第一位为符号位,所以最后的结果就为 -0x7fffffff = -2147483647  
13. */  
14.System.out.println(-0x7fffffff);// -2147483647  
System.out.println(Integer.MIN_VALUE == -Integer.MIN_VALUE);// true 
/*
 *  0x80000000取反得0x7fffffff,再加1得0x80000000,因为负数是
 *  以补码形式存储于内存中的,所以推导出结果原码为:0x80000000,
 *  即为-0,又因为-0是等于0的,所以不需要-0这个编码位,那就多了
 *  一个0x80000000编码位了,所以最后就规定0x80000000为最小负数 
 */
System.out.println(-0x80000000);// -2147483648
/*
 *  0x7fffffff取反得0x80000000,再加1得0x80000001,因为负数是
 *  以补码形式存储于内存中的,所以推导出结果原码为:0xffffffff,
*  第一位为符号位,所以最后的结果就为 -0x7fffffff = -2147483647
 */
System.out.println(-0x7fffffff);// -2147483647 
另外,还发现有趣现象:最大整数加1后会等于最小整数:

Java代码 
1.// MAX_VALUE = 0x7fffffff; MIN_VALUE = 0x80000000;   
2.System.out.println((Integer.MAX_VALUE + 1) == Integer.MIN_VALUE);// true   
3.// MIN_VALUE = 0x8000000000000000L; MIN_VALUE = 0x8000000000000000L;   
4.System.out.println((Long.MAX_VALUE + 1) == Long.MIN_VALUE);// true  
// MAX_VALUE = 0x7fffffff; MIN_VALUE = 0x80000000;
System.out.println((Integer.MAX_VALUE + 1) == Integer.MIN_VALUE);// true
// MIN_VALUE = 0x8000000000000000L; MIN_VALUE = 0x8000000000000000L;
System.out.println((Long.MAX_VALUE + 1) == Long.MIN_VALUE);// true当然,-Byte. MIN_VALUE==Byte.MIN_VALUE、-Short.MIN_VALUE== Short.MIN_VALUE、-Long.MIN_VALUE== Long.MIN_VALUE,也是成立的。


16. Math.abs结果一定为非负数吗?
Java代码 
1.System.out.println(Math.abs(Integer.MIN_VALUE));// -2147483648  
System.out.println(Math.abs(Integer.MIN_VALUE));// -2147483648上面的程序不会输出2147483648,而是-2147483648,为什么?

 

其实我们看一下Math.abs源码就知道为什么了,源码:(a < 0) ? -a : a;,结合上面那个迷题,我们就发现-Integer.MIN_VALUE= Integer.MIN_VALUE,所以上面的答案就是最小整数自己。

 

另外我们也可以从API文档看到对Math.abs()方法的解释:如果参数等于 Integer.MIN_VALUE 的值(即能够表示的最

小负 int 值),则结果与该值相同且为负。

 

所以Math.abs不能保证一定会返回非负结果。

 

当然,Long.MIN_VALUE也是这样的。

 

 

 

17. 不要使用基于减法的比较器
Java代码 
1.Comparator<Integer> c = new Comparator<Integer>() {   
2. public int compare(Integer i1, Integer i2) {   
3.  return i1 - i2;// 升序   
4. }   
5.};   
6.List<Integer> l = new ArrayList<Integer>();   
7.l.add(new Integer(-2000000000));   
8.l.add(new Integer(2000000000));   
9.Collections.sort(l, c);   
10.System.out.println(l);// [2000000000, -2000000000]  
Comparator<Integer> c = new Comparator<Integer>() {
 public int compare(Integer i1, Integer i2) {
  return i1 - i2;// 升序
 }
};
List<Integer> l = new ArrayList<Integer>();
l.add(new Integer(-2000000000));
l.add(new Integer(2000000000));
Collections.sort(l, c);
System.out.println(l);// [2000000000, -2000000000]上面程序的比较器是升序,结果却不是这样,比较时出现了什么问题?

 

先看看下面程序片断:

Java代码 
1.int x = -2000000000;   
2.int y = 2000000000;   
3./*  
4. * -2000000000 即 -(01110111001101011001010000000000)  
5. * 的补码为:                10001000110010100110110000000000  
6. *   
7. * 计算过程使用竖式表示:  
8. * 10001000110010100110110000000000  
9. * 10001000110010100110110000000000  
10. * --------------------------------  
11. * 00010001100101001101100000000000  
12. *   
13. * 计算结果溢出,结果为294967296  
14. */  
15.System.out.println(x - y);// 294967296  
int x = -2000000000;
int y = 2000000000;
/*
 * -2000000000 即 -(01110111001101011001010000000000)
 * 的补码为:                10001000110010100110110000000000
 * 
 * 计算过程使用竖式表示:
 * 10001000110010100110110000000000
 * 10001000110010100110110000000000
 * --------------------------------
 * 00010001100101001101100000000000
 * 
 * 计算结果溢出,结果为294967296
 */
System.out.println(x - y);// 294967296所以不要使用减法的比较器,除非能确保要比较的数值之间的距离永远不会大于Intger. MAX_VALUE。

 

基于整型的比较器的实现一般使用如下的方式来比较:

Java代码 
1.public int compare(Integer i1, Integer i2) {   
2. return (i1 < i2 ? -1 : (i1 == i2 ? 0 : 1));   
3.}  
public int compare(Integer i1, Integer i2) {
 return (i1 < i2 ? -1 : (i1 == i2 ? 0 : 1));

18.  int i=-2147483648与int i=-(2147483648)?
Java代码 
1.int i=-(2147483648);  
int i=-(2147483648);编译通不过!为什么

 

int字面常量2147483638只能作为一元负操作符的操作数来使用。

 

类似的还有最大long:

Java代码 
1.long i=–(9223372036854775808L);  
long i=–(9223372036854775808L); 
字符串
19. char类型相加
Java代码 
1.System.out.println('a' + 'A');//162  
System.out.println('a' + 'A');//162上面的结果不是 aA ,而是 162。


当且仅当+操作符的操作数中至少有一个是String类型时,才会执行字符串连接操作;否则,执行加法。如果要连接的

数值没有一个是字符串类型的,那么你可以有几种选择:预置一个空字符串("" + 'a' + 'A');将第一个数值用

String.valueOf()显示地转换成一个字符串(String.valueOf('a') + 'A');使用一个字符串缓冲区(sb.append

('a');sb.append('A'););或者如果使用的是JDK5.0,可以用printf(System.out.printf("%c%c",'a','A'));


20. 程序中的Unicode转义字符
Java代码 
1.//\u0022是双引号的Unicode编码表示   
2.System.out.println("a\u0022.length() + \u0022b".length());// 2  
//\u0022是双引号的Unicode编码表示
System.out.println("a\u0022.length() + \u0022b".length());// 2Unicode编码表示的字符是在编译期间就转换成了普通字符,它与普通转义字符(如:\")是不一样的,它们是在程序

被解析为各种符号之后才处理转义字符序列。

 

21. 注释中的Unicode转义字符
如果程序中含有以下的注释:// d:\a\b\util ,程序将不能编译通过,原因是\u后面跟的不是四个十六进制数字,但

编译器在编译时却要把\u开头的字符的字符看作是Unicode编码表示的字符。

 

所以要注意:注释中也是支持Unicode转义字符的。

 

另外一个问题是不能在注释行的中间含有 \u000A 表示换行的Unicode字符,因为这样在编译时读到 \u000A 时,表示

行结束,那么后面的字符就会当作程序代码而不在是注释了。


22. Windows与Linux上的行结束标示符
Java代码 
1.String line = (String)System.getProperties().get("line.separator");   
2.for(int i =0; i < line.length();i++){   
3. System.out.println((int)line.charAt(i));   
4.}  
String line = (String)System.getProperties().get("line.separator");
for(int i =0; i < line.length();i++){
 System.out.println((int)line.charAt(i));
}在Windows上运行结果:
13
10
在Linux上运行的结果:
10

在Windows平台上,行分隔符是由回车(\r)和紧其后的换行(\n)组成,但在Unix平台上通常使用单独的换行(\n)

表示。


23. 输出0-255之间的ISO8859-1符
Java代码 
1.byte bts[] = new byte[256];   
2.for (int i = 0; i < 256; i++) {   
3. bts[i] = (byte) i;   
4.}   
5.// String str = new String(bts,"ISO8859-1");//正确的做法   
6.String str = new String(bts);//使用操作系统默认编码方式编码(XP GBK)   
7.for (int i = 0, n = str.length(); i < n; i++) {   
8. System.out.print((int) str.charAt(i) + " ");   
9.}  
byte bts[] = new byte[256];
for (int i = 0; i < 256; i++) {
 bts[i] = (byte) i;
}
// String str = new String(bts,"ISO8859-1");//正确的做法
String str = new String(bts);//使用操作系统默认编码方式编码(XP GBK)
for (int i = 0, n = str.length(); i < n; i++) {
 System.out.print((int) str.charAt(i) + " ");
}上面不会输出0-255之间的数字串,正确的方式要使用new String(bts," ISO8859-1") 方式来解码。

 

ISO8859-1是唯一能够让该程序按顺序打印从0到255的整数的缺少字符集,这也是唯一在字符和字节之间一对一的映射

字符集。

 

通过java获取操作系统的默认编码方式:

Java代码 
1.System.getProperty("file.encoding");//jdk1.4或之前版本   
2.java.nio.charset.Charset.defaultCharset();//jdk1.5或之后版本  
System.getProperty("file.encoding");//jdk1.4或之前版本
java.nio.charset.Charset.defaultCharset();//jdk1.5或之后版本 
24. String的replace()与replaceAll()
Java代码 
1.System.out.println(".".replaceAll(".class", "\\$"));  
System.out.println(".".replaceAll(".class", "\\$"));上面程序将 . 替换成 \$,但运行时报异常,主要原replaceAll的第二参数有两个字符(\ $)是特殊字符,具有特殊

意思(\用来转移 \ 与 $,$后面接数字表示反向引用)。另外,replaceAll的第一参数是正则表达式,所以要注意特

殊字符,正确的作法有以下三种:

Java代码 
1.System.out.println(".class".replaceAll("\\.", "\\\\\\$"));   
2.System.out.println(".class".replaceAll("\\Q.\\E", "\\\\\\$"));   
3.System.out.println(".class".replaceAll(Pattern.quote("."), Matcher.quoteReplacement("\\$")));  
System.out.println(".class".replaceAll("\\.", "\\\\\\$"));
System.out.println(".class".replaceAll("\\Q.\\E", "\\\\\\$"));
System.out.println(".class".replaceAll(Pattern.quote("."), Matcher.quoteReplacement("\\$")));API对\、\Q与\E的解释: 
\  引用(转义)下一个字符 
\Q引用所有字符,直到 \E 
\E结束从 \Q 开始的引用

 

JDK5.0新增了一些解决此问题的新方法:
java.util.regex.Pattern.quote(String s):使用\Q与\E将参数引起来,这些被引用的字符串就是一般的字符,哪怕

含有正则式特殊字符。
java.util.regex.Matcher.quoteReplacement(String s):将\与$转换成能应用于replaceAll第二个参数的字符串,

即可作为替换内容。

String的replace(char oldChar, char newChar)方法却不使用正则式,但它们只支持字符,而不是字符串,使用起来

受限制:

Java代码 
1.System.out.println(".".replace('.','\\'));//能将 . 替换成 \   
2.System.out.println(".".replace('.','$')); //能将 . 替换成 $  
System.out.println(".".replace('.','\\'));//能将 . 替换成 \
System.out.println(".".replace('.','$')); //能将 . 替换成 $ 
25. 一段程序的三个Bug
Java代码 
1.Random rnd = new Random();   
2.StringBuffer word = null;   
3.switch (rnd.nextInt(2)) {   
4.case 1:   
5. word = new StringBuffer('P');   
6.case 2:   
7. word = new StringBuffer('G');   
8.default:   
9. word = new StringBuffer('M');   
10.}   
11.word.append('a');   
12.word.append('i');   
13.word.append('n');   
14.System.out.println(word);  
Random rnd = new Random();
StringBuffer word = null;
switch (rnd.nextInt(2)) {
case 1:
 word = new StringBuffer('P');
case 2:
 word = new StringBuffer('G');
default:
 word = new StringBuffer('M');
}
word.append('a');
word.append('i');
word.append('n');
System.out.println(word);上面的程序目的是等概率的打印 Pain、Gain、Main 三个单词,但多次运行程序却发现永远只会打印 ain,这是为什

么?

 

第一个问题在于:rnd.nextInt(2)只会返回0、1 两个数字,所以上面只会走case 1: 的分支语句,case 2: 按理是永

远不会走的。

第二个问题在于:如果case语句不以break结束时,则一直会往向运行,即直到执行到break的case语句止,所以上面

的的语句每次都会执行default分支语句。

第三个问题在于:StringBuffer的构造函数有两种可接受参数的,一个是StringBuffer(int capacity)、另一个是

StringBuffer(String str),上面用的是StringBuffer(char)构造函数,实质上运行时将字符型转换成了int型,这样

将字符当作StringBuffer的初始容量了,而不是字符本身。

 

以下是修改后的程序片段:

Java代码 
1.Random rnd = new Random();   
2.StringBuffer word = null;   
3.switch (rnd.nextInt(3)) {   
4.case 1:   
5. word = new StringBuffer("P");   
6. break;   
7.case 2:   
8. word = new StringBuffer("G");   
9. break;   
10.default:   
11. word = new StringBuffer("M");   
12. break;// 可以不要   
13.  
14.}   
15.word.append('a');   
16.word.append('i');   
17.word.append('n');   
18.System.out.println(word); 

 

 

 

异常
26. finally与中断
Java代码 
1.//该方法返回false   
2.static boolean f() {   
3. try {   
4.  return true;   
5. } finally {   
6.  return false;   
7. }   
8.}  
//该方法返回false
static boolean f() {
 try {
  return true;
 } finally {
  return false;
 }
}不要用return、break、continue或throw来退出finally语句块,并且千万不要允许受检查的异常传播到finally语句

块之外。也就是说不要在finally块内终止程序,而是执行完finally块后,要将控制权移交给try块,由try最终决定

怎样结束方法的调用。

 

对于任何在finally语句块中可能抛出的受检查异常都要进行处理,而不是任其传播,下面流拷贝程序在关闭流时没有

防止异常的传播,这会有问题:

Java代码 
1.static void copy(String src, String dest) throws IOException {   
2. InputStream in = null;   
3. OutputStream out = null;   
4. try {   
5.  in = new FileInputStream(src);   
6.  out = new FileOutputStream(dest);   
7.  byte[] buf = new byte[1024];   
8.  int n;   
9.  while ((n = in.read(buf)) >= 0) {   
10.   out.write(buf, 0, n);   
11.  }   
12. } finally{   
13.  //这里应该使用try-catch将每个close包装起来   
14.  if(in != null){in.close();}   
15.  if(in != null){out.close();}   
16. }   
17.}  
static void copy(String src, String dest) throws IOException {
 InputStream in = null;
 OutputStream out = null;
 try {
  in = new FileInputStream(src);
  out = new FileOutputStream(dest);
  byte[] buf = new byte[1024];
  int n;
  while ((n = in.read(buf)) >= 0) {
   out.write(buf, 0, n);
  }
 } finally{
  //这里应该使用try-catch将每个close包装起来
  if(in != null){in.close();}
  if(in != null){out.close();}
 }
}catch块中的return语句是不会阻止finally块执行的,那么catch块中的continue和break能否阻止?答案是不会的,

与return一样,finally语句块是在循环被跳过(continue)和中断(break)之前被执行的:

Java代码 
1.int i = 0;   
2.System.out.println("--continue--");   
3.while (i++ <= 1) {   
4. try {   
5.  System.out.println("i=" + i);   
6.  continue;   
7. } catch (Exception e) {   
8. } finally {   
9.  System.out.println("finally");   
10. }   
11.}   
12.System.out.println("--break--");   
13.while (i++ <= 3) {   
14. try {   
15.  System.out.println("i=" + i);   
16.  break;   
17. } catch (Exception e) {   
18. } finally {   
19.  System.out.println("finally");   
20. }   
21.}  
int i = 0;
System.out.println("--continue--");
while (i++ <= 1) {
 try {
  System.out.println("i=" + i);
  continue;
 } catch (Exception e) {
 } finally {
  System.out.println("finally");
 }
}
System.out.println("--break--");
while (i++ <= 3) {
 try {
  System.out.println("i=" + i);
  break;
 } catch (Exception e) {
 } finally {
  System.out.println("finally");
 }
}27. catch捕获异常规则
捕获RuntimeException、Exception或Throwable的catch语句是合法,不管try块里是否抛出了这三个异常。但如果try

块没有抛出或不可能抛出检测性异常,则catch不能捕获这些异常,如IOException异常:

Java代码 
1.public class Test {   
2. public static void main(String[] args) {   
3.  try{   
4.   //...   
5.  }catch (Exception e) {   
6.      
7.  }catch (Throwable e) {   
8.      
9.  }   
10.     
11.  /* !! 编译出错  
12.   try{  
13.    //...  
14.   }catch (IOException e) {  
15.      
16.   }  
17.   */  
18. }   
19.}  
public class Test {
 public static void main(String[] args) {
  try{
   //...
  }catch (Exception e) {
   
  }catch (Throwable e) {
   
  }
  
  /* !! 编译出错
   try{
    //...
   }catch (IOException e) {
    
   }
   */
 }
}28. 重写时方法异常范围
重写或实现时不能扩大异常的范围,如果是多继承,则异常取所有父类方法异常的交集或不抛出异常:

Java代码 
1.interface I1 {   
2. void f() throws Exception;   
3.}   
4.  
5.interface I2 {   
6. void f() throws IOException;   
7.}   
8.  
9.interface I3 extends I1, I2 {}   
10.  
11.class Imp implements I3 {   
12. // 不能编译通过,多继承时只能取父类方法异常交集,这样就不会扩大异常范围   
13. // !! void f () throws Exception;   
14. // void f();// 能编译通过   
15. // 能编译通过,Exception与IOException的交集为IOException   
16. public void f() throws IOException {   
17. }   
18.}  
interface I1 {
 void f() throws Exception;
}

interface I2 {
 void f() throws IOException;
}

interface I3 extends I1, I2 {}

class Imp implements I3 {
 // 不能编译通过,多继承时只能取父类方法异常交集,这样就不会扩大异常范围
 // !! void f () throws Exception;
 // void f();// 能编译通过
 // 能编译通过,Exception与IOException的交集为IOException
 public void f() throws IOException {
 }
}29. 静态与非静态final常量不能在catch块中初始化

 

静态与非静态块中如果抛出了异常,则一定要使用try-catch块来捕获。

Java代码 
1.public class Test {   
2. static final int i;   
3. static {   
4.  try {   
5.   i = f();   
6.  } catch (RuntimeException e) {   
7.   i = 1;   
8.  }   
9. }   
10.  
11. static int f() {   
12.  throw new RuntimeException();   
13. }   
14.}  
public class Test {
 static final int i;
 static {
  try {
   i = f();
  } catch (RuntimeException e) {
   i = 1;
  }
 }

 static int f() {
  throw new RuntimeException();
 }
}上面的程序编译不能通过。表面上是可以的,因为i第一次初始化时可能抛出异常,所以抛异常时可以在catch块中初

始化,最终还是只初始化一次,这正是空final所要求的,但为什么编译器不知道这些呢?

 

要确定一个程序是否不止一次地对一个空final进行赋值是很困难的问题。语言规范在这一点上采用了保守的方式。

30. System.exit()与finally
Java代码 
1.try {   
2. System.out.println("Hello world");   
3. System.exit(0);   
4. // 或者使用Runtime退出系统   
5. // Runtime.getRuntime().exit(0);   
6.} finally {   
7. System.out.println("Goodbyte world");   
8.}  
try {
 System.out.println("Hello world");
 System.exit(0);
 // 或者使用Runtime退出系统
 // Runtime.getRuntime().exit(0);
} finally {
 System.out.println("Goodbyte world");
}上面的程序会打印出"Goodbyte world"吗?不会。

 

System.exit将立即停止所有的程序线程,它并不会使finally语句块得到调用,但是它在停止VM之前会执行关闭挂钩

操作(这此挂钩操作是注册到Runtime.addShutdownHook上的线程),这对于释放VM之外的资源很有帮助。使用挂钩程

序修改上面程序:

Java代码 
1.System.out.println("Hello world");   
2.Runtime.getRuntime().addShutdownHook(new Thread() {   
3. public void run() {   
4.  System.out.println("Goodbyte world");   
5. }   
6.});   
7.System.exit(0);  
System.out.println("Hello world");
Runtime.getRuntime().addShutdownHook(new Thread() {
 public void run() {
  System.out.println("Goodbyte world");
 }
});
System.exit(0);

另外,对象回收时,使用VM调用对象的finalize()方法有两种:
System.runFinalization():该方法让虚拟机也只是尽最大努力去完成所有未执行的finalize()终止方法,但不一定

会执行。
System.runFinalizersOnExit(true):该方法一定会回收,但不安全,已被废弃。因为它可能对正在使用的对象调用

终结方法,而其他线程同时正在操作这些对象,从而导致不正确的行为或死锁。

 

为了加快垃圾回收,使用System.gc(),但不一定马上执行加收动作,由虚拟机决定,实质上是调用

Runtime.getRuntime().gc()。

 

System的很多方法都是调用Runtime类的相关方法来实现的。


31. 递归构造
Java代码 
1.public class S  {   
2. private S instance = new S();   
3. public S() {}   
4.}  
public class S  {
 private S instance = new S();
 public S() {}
}如果在程序外面构造该类的实例,则会抛出java.lang.StackOverflowError错误。其原因是实例变量的初始化操作将

先于构造器的程序体而运行。


32. 构造器中的异常
如果父类构造器抛出了检测异常,则子类也只能抛出,而不能采用try-catch来捕获:

Java代码 
1.public class P {   
2. public P() throws Exception {}   
3.}   
4.  
5.class S extends P {   
6. public S() throws Exception {   
7.  try {   
8.   // 不能在try块中明确调用父类构造器,因为构造的   
9.   // 明确调用只能放在第一行   
10.   // !! super();   
11.  //try-catch不能捕获到父类构造器所抛出的异常,子类只能抛出   
12.  } catch (Exception e) {   
13.  }   
14. }   
15.}  
public class P {
 public P() throws Exception {}
}

class S extends P {
 public S() throws Exception {
  try {
   // 不能在try块中明确调用父类构造器,因为构造的
   // 明确调用只能放在第一行
   // !! super();
  //try-catch不能捕获到父类构造器所抛出的异常,子类只能抛出
  } catch (Exception e) {
  }
 }

如果初使化实例属性时抛出了异常,则构造器只能抛出异常,在构造器中捕获不起作用:

Java代码 
1.public class A {   
2.    private String str = String.class.newInstance();   
3.  
4.    public A() throws InstantiationException, IllegalAccessException {}   
5.  
6.    public A(int i) throws Exception {   
7.        try {//即使这里捕获了,方法签名还是得要抛出   
8.  
9.        } catch (Exception e) {   
10.  
11.        }   
12.    }   
13.  
14.    /*  
15.     * !!编译不能通过,因为str2为静态的,他不能通过构造器来捕获,所以只  
16.     * 能使用静态方法来捕获。即初始化静态成员时不能抛出捕获性异常。   
17.     */  
18.    //!!private static String str2 = String.class.newInstance();   
19.       
20.    // 只能使用静态方法来捕获异常,如果是抛出的运行时异常则不需要捕获   
21.    private static String str2 = newInstance();   
22.  
23.    private static String newInstance() throws RuntimeException {   
24.        try {   
25.            return String.class.newInstance();   
26.        } catch (Exception e) {   
27.            e.printStackTrace();   
28.        }   
29.        return null;   
30.    }   
31.}  
public class A {
 private String str = String.class.newInstance();

 public A() throws InstantiationException, IllegalAccessException {}

 public A(int i) throws Exception {
  try {//即使这里捕获了,方法签名还是得要抛出

  } catch (Exception e) {

  }
 }

 /*
  * !!编译不能通过,因为str2为静态的,他不能通过构造器来捕获,所以只
  * 能使用静态方法来捕获。即初始化静态成员时不能抛出捕获性异常。 
  */
 //!!private static String str2 = String.class.newInstance();
 
 // 只能使用静态方法来捕获异常,如果是抛出的运行时异常则不需要捕获
 private static String str2 = newInstance();

 private static String newInstance() throws RuntimeException {
  try {
   return String.class.newInstance();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return null;
 }
}33. StackOverflowError
Java虚拟机对栈的深度限制到了某个值,当超过这个值时,VM就抛出StackOverflowError。一般VM都将栈的深度限制

为1024,即当方法调用方法的层次超过1024时就会产生StackOverflowError。

 

34. 参数兼容的方法重载
Java代码 
1.public class Confusing {   
2. private Confusing(Object o) {   
3.  System.out.println("Object");   
4. }   
5. private Confusing(double[] dArr) {   
6.  System.out.println("double array");   
7. }   
8. public static void main(String[] args) {   
9.  new Confusing(null);   
10. }   
11.}  
public class Confusing {
 private Confusing(Object o) {
  System.out.println("Object");
 }
 private Confusing(double[] dArr) {
  System.out.println("double array");
 }
 public static void main(String[] args) {
  new Confusing(null);
 }
}上面的程序打印的是“double array”,为什么?

 

null可代表任何非基本类型对象。

 

Java的重载解析过程是分两阶段运行的。第一阶段选取所有可获得并且可应用的方法或构造器。第二阶段在第一阶段

选取的方法或构造器中选取最精确的一个。如果一个方法或构造器可以接受传递给另一个方法或构造器的任何参数,

那么我们就说第一个方法比第二个方法缺乏精确性,调用时就会选取第二个方法。

 

使用上面的规则来解释该程序:构造器Confusing(Object o)可以接受任何传递Confusing(double[] dArr)的参数,因

此Confusing(Object o)相对缺乏精确性,所以Confusing(null)会调用Confusing(double[] dArr)构造器。

 

如果想强制要求编译器选择一个自己想要的重载版本,需要将实参强制转型为所需要的构造器或方法的参数类型:如

这里要调用Confusing(Object o)本版,则这样调用:Confusing((Object)null)。

 

如果你确实进行了重载,那么请确保所有的重载版本所接受的参数类型都互不兼容,这样,任何两个重载版本都不会

同时是可应用的。


35. 静态方法不具有多态特性
Java代码 
1.class A1 {   
2. public static void f() {   
3.  System.out.println("A1.f()");   
4. }   
5.}   
6.class A2 extends A1 {   
7. public static void f() {   
8.  System.out.println("A2.f()");   
9. }   
10.}   
11.class T {   
12. public static void main(String[] args) {   
13.  A1 a1 = new A1();   
14.  A1 a2 = new A2();   
15.  // 静态方法不具有多态效果,它是根据引用声明类型来调用   
16.  a1.f();// A1.f()   
17.  a2.f();// A1.f()   
18. }   
19.}  
class A1 {
 public static void f() {
  System.out.println("A1.f()");
 }
}
class A2 extends A1 {
 public static void f() {
  System.out.println("A2.f()");
 }
}
class T {
 public static void main(String[] args) {
  A1 a1 = new A1();
  A1 a2 = new A2();
  // 静态方法不具有多态效果,它是根据引用声明类型来调用
  a1.f();// A1.f()
  a2.f();// A1.f()
 }
}对静态方法的调用不存在任何动态的分派机制。当一个程序调用了一个静态方法时,要被调用的方法都是在编译时就

被选定的,即调用哪个方法是根据该引用被声明的类型决定的。上面程序中a1与a2引用的类型都是A1类型,所以调用

的是A1中的f()方法。

分享到:
评论

相关推荐

    java解惑(+Java 解惑你知多少)

    你认为自己了解Java多少?你是个爱琢磨的代码侦探吗?你是否曾经花费数天时间去追踪一个由Java或其类库的陷阱和缺陷而导致的bug?你喜欢智力测验吗?本书正好适合你!.. Bloch和Gafter继承了Effective Jaya一书的传统,...

    java 解惑 java 解惑 java 解惑

    java 解惑 java 解惑 java 解惑 java 解惑 java 解惑 java 解惑

    Java解惑(中文版)_java_java解惑_solve65p_

    《Java解惑(中文版)》是一本专为Java初学者设计的学习资料,旨在帮助读者解答在学习Java过程中遇到的各种困惑。"solve65p"可能代表这本书包含65个问题或主题,每个都深入浅出地进行了讲解,旨在解决初学者在编程...

    JAVA 解惑 java经典

    1. **Java基础**:这可能包括变量、数据类型、运算符、控制结构(如if语句、for循环、while循环)、方法、类和对象等基本概念。理解这些是Java编程的基础,对于初学者来说至关重要。 2. **面向对象编程**:Java是一...

    Java PUZZLE Java 解惑

    Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑

    Java解惑 中文版

    《Java解惑中文版》是一本专为Java程序员设计的指南,旨在帮助读者解决在编程过程中遇到的各种问题,提升程序的健壮性。本书深入浅出地探讨了Java语言的核心概念、常见疑惑以及最佳实践,旨在使开发者能够编写出更...

    Java解惑.pdf

    1. **基础语法**:Java的基础语法包括变量、数据类型、运算符、流程控制(如if-else,switch,for,while等)、方法定义与调用等。理解这些基础知识是编写任何Java程序的前提。 2. **类与对象**:面向对象编程是...

    java解惑 PDF版

    文档《java解惑 PDF版》中列举了95个这样的谜题,每个谜题都旨在帮助开发者理解并纠正一些常见的错误理解。以下是根据提供的部分内容解析的几个相关知识点。 ### 表达式谜题与取余操作符(%)的行为 在Java中,...

    "java解惑" PDF版本

    "java解惑" PDF版本

    JAVA解惑.pdf

    《JAVA解惑》这本书主要针对Java编程中遇到的各种常见问题和困惑进行了解答,旨在帮助开发者深入理解Java语言,提高编程技巧。以下是一些关键的知识点解析: 1. **异常处理**:Java中的异常处理是通过try-catch-...

    Java解惑 布洛克 著;陈昊鹏 译

    《Java解惑》 布洛克 著;陈昊鹏 译 扫描清晰带目录,仅供参阅,请支持正版

    java解惑java解惑java解惑

    1. **Java基础** - **变量与数据类型**:Java支持基本数据类型(如int、double等)和引用类型(如类、接口和数组)。理解它们的区别对于正确使用变量至关重要。 - **控制流**:包括if条件语句、for、while循环以及...

    4,JAVA解惑 高清PDF 下载

    《JAVA解惑》是Java开发者领域的一本经典著作,它被广大...总之,《JAVA解惑》是一本涵盖了Java核心知识点、实战技巧和高级特性的宝典,无论你是Java新手还是老手,都能从中受益匪浅,解决你在Java编程中的种种疑惑。

    Java解惑Java解惑

    《Java解惑》系列是一本专注于解答Java编程中常见困惑的书籍,由作者junJZ_2008在JavaEye社区分享。该系列共有八篇文章,内容涵盖了许多基础但重要的Java知识点,旨在帮助读者深化对Java语言的理解,尤其是那些在...

    java解惑 for all javaer

    讲述如何在程序中避免程序缺陷和程序陷阱的,解惑的过程中,介绍了一些Java编程语言中许多不易被掌握的知识点,其阅读价值非常高,适合具有Java知识的学习者和有编程经验的Java程序员阅读。

    JAVA解惑.pfd

    JAVA解惑

    JAVA解惑(JAVA谜题) 中文版(PDF)

    ### JAVA解惑中的关键知识点解析 #### 谜题1:奇数性的判断 在《JAVA解惑》一书中,作者通过一系列实例介绍了Java编程语言中的一些不易掌握的知识点。其中一个例子是关于如何正确判断一个整数是否为奇数。 **原始...

    Java解惑(中文).pdf

    《Java解惑(中文)》是一本专门为Java开发者编写的指南书籍,旨在解决在实际编程过程中遇到的各种疑惑和难题。本书以中文语言呈现,使得国内的Java程序员能够更轻松地理解并应用其中的知识。通过阅读这本书,读者...

Global site tag (gtag.js) - Google Analytics