[轉(zhuǎn)]Java語言的細(xì)節(jié)
Java作為一門優(yōu)秀的面向?qū)ο蟮某绦蛟O(shè)計(jì)語言,正在被越來越多的人使用。本文試圖列出作者在實(shí)際開發(fā)中碰到的一些Java語言的容易被人忽視的細(xì)節(jié),希望能給正在學(xué)習(xí)Java語言的人有所幫助。?
1,拓寬數(shù)值類型會(huì)造成精度丟失嗎?
??? Java語言的8種基本數(shù)據(jù)類型中7種都可以看作是數(shù)值類型,我們知道對(duì)于數(shù)值類型的轉(zhuǎn)換有一個(gè)規(guī)律:從窄范圍轉(zhuǎn)化成寬范圍能夠自動(dòng)類型轉(zhuǎn)換,反之則必須強(qiáng)制轉(zhuǎn)換。請(qǐng)看下圖:
byte-->short-->int-->long-->float-->double
char-->int
我們把順箭頭方向的轉(zhuǎn)化叫做拓寬類型,逆箭頭方向的轉(zhuǎn)化叫做窄化類型。一般我們認(rèn)為因?yàn)轫樇^方向的轉(zhuǎn)化不會(huì)有數(shù)據(jù)和精度的丟失,所以Java語言允許自動(dòng)轉(zhuǎn)化,而逆箭頭方向的轉(zhuǎn)化可能會(huì)造成數(shù)據(jù)和精度的丟失,所以Java語言要求程序員在程序中明確這種轉(zhuǎn)化,也就是強(qiáng)制轉(zhuǎn)換。那么拓寬類型就一定不會(huì)造成數(shù)據(jù)和精度丟失嗎?請(qǐng)看下面代碼:
int i=2000000000;
int num=0;
for(float f=i;f<i+50;f++){
??? num++;
}
System.out.println(num);
請(qǐng)考察以上代碼輸出多少?
如果你回答50 ,那么請(qǐng)運(yùn)行一下,結(jié)果會(huì)讓你大吃一驚!沒錯(cuò),輸出結(jié)果是0,難道這個(gè)循環(huán)根本就沒有執(zhí)行哪怕一次?確實(shí)如此,如果你還不死心,我?guī)憧匆粋€(gè)更詫異的現(xiàn)象,運(yùn)行以下代碼,看輸出什么?
int i=2000000000;
float f1=i;
float f2=i+50;
System.out.println(f1==f2);
??? 哈哈,你快要不相信你的眼睛了,結(jié)果竟然是true;難道f1和f2是相等的嗎?是的,就是這樣,這也就能解釋為什么上一段代碼輸出的結(jié)果是0,而不是50了。那為什么會(huì)這樣呢?關(guān)鍵原因在于你將int值自動(dòng)提升為float時(shí)發(fā)生了數(shù)據(jù)精度的丟失,i的初始值是2000000000,這個(gè)值非常接近Integer.MAX_VALUE,因此需要用31位來精確表示,而float只能提供24位數(shù)據(jù)的精度(另外8位是存儲(chǔ)位權(quán),見IEEE745浮點(diǎn)數(shù)存儲(chǔ)規(guī)則)。所以在這種自動(dòng)轉(zhuǎn)化的過程中,系統(tǒng)會(huì)將31位數(shù)據(jù)的前24位保留下來,而舍棄掉最右邊的7位,所以不管是2000000000還是2000000050,舍棄掉最右邊7位后得到的值是一樣的。這就是為什么f1==f2的原因了。
??? 類似的這種數(shù)值拓寬類型的過程中會(huì)造成精度丟失的還有兩種情況,那就是long轉(zhuǎn)化成float和long轉(zhuǎn)化成double,所以在使用的時(shí)候一定要小心。
?
2,i=i+1和i+=1完全等價(jià)嗎?
??? 可能有很多程序員認(rèn)為i+=1只是i=i+1的簡寫方式,其實(shí)不然,它們一個(gè)使用簡單賦值運(yùn)算,一個(gè)使用復(fù)合賦值運(yùn)算,而簡單賦值運(yùn)算和復(fù)合賦值運(yùn)算的最大差別就在于:復(fù)合賦值運(yùn)算符會(huì)自動(dòng)地將運(yùn)算結(jié)果轉(zhuǎn)型為其左操作數(shù)的類型。看看以下的兩種寫法,你就知道它們的差別在哪兒了:
? (1) byte i=5;
????? i+=1;
? (2) byte i=5;
????? i=i+1;
??? 第一種寫法編譯沒問題,而第二種寫法卻編譯通不過。原因就在于,當(dāng)使用復(fù)合賦值運(yùn)算符進(jìn)行操作時(shí),即使右邊算出的結(jié)果是int類型,系統(tǒng)也會(huì)將其值轉(zhuǎn)化為左邊的byte類型,而使用簡單賦值運(yùn)算時(shí)沒有這樣的優(yōu)待,系統(tǒng)會(huì)認(rèn)為將i+1的值賦給i是將int類型賦給byte,所以要求強(qiáng)制轉(zhuǎn)換。理解了這一點(diǎn)后,我們?cè)賮砜匆粋€(gè)例子:
? byte b=120;
? b+=20;
? System.out.println("b="+b);
? 說到這里你應(yīng)該明白了,上例中輸出b的值不是140,而是-116。因?yàn)?20+20的值已經(jīng)超出了一個(gè)byte表示的范圍,而當(dāng)我們使用復(fù)合賦值運(yùn)算時(shí)系統(tǒng)會(huì)自動(dòng)作類型的轉(zhuǎn)化,將140強(qiáng)轉(zhuǎn)成byte,所以得到是-116。由此可見,在使用復(fù)合賦值運(yùn)算符時(shí)還得小心,因?yàn)檫@種類型轉(zhuǎn)換是在不知不覺中進(jìn)行的,所以得到的結(jié)果就有可能和你的預(yù)想不一樣。
?
3,位移運(yùn)算越界怎么處理
??? 考察下面的代碼輸出結(jié)果是多少?
??? int a=5;
??? System.out.println(a<<33);
??? 按照常理推測,把a(bǔ)左移33位應(yīng)該將a的所有有效位都移出去了,那剩下的都是零啊,所以輸出結(jié)果應(yīng)該是0才對(duì)啊,可是執(zhí)行后發(fā)現(xiàn)輸出結(jié)果是10,為什么呢?因?yàn)镴ava語言對(duì)位移運(yùn)算作了優(yōu)化處理,Java語言對(duì)a<<b轉(zhuǎn)化為a<<(b%32)來處理,所以當(dāng)要移位的位數(shù)b超過32時(shí),實(shí)際上移位的位數(shù)是b%32的值,那么上面的代碼中a<<33相當(dāng)于a<<1,所以輸出結(jié)果是10。
?
4,判斷奇數(shù)
? 以下的方法判斷某個(gè)整數(shù)是否是奇數(shù),考察是否正確:
?? public boolean isOdd(int n){
?????? return (n%2==1);
?? }
?? 很多人認(rèn)為上面的代碼沒問題,但實(shí)際上這段代碼隱藏著一個(gè)非常大的BUG,當(dāng)n的值是正整數(shù)時(shí),以上的代碼能夠得到正確結(jié)果,但當(dāng)n的值是負(fù)整數(shù)時(shí),以上方法不能做出正確判斷。例如,當(dāng)n=-3時(shí),以上方法返回false。因?yàn)楦鶕?jù)Java語言規(guī)范的定義,Java語言里的求余運(yùn)算符(%)得到的結(jié)果與運(yùn)算符左邊的值符號(hào)相同,所以,-3%2的結(jié)果是-1,而不是1。那么上面的方法正確的寫法應(yīng)該是:
?? public boolean isOdd(int n){
?????? return (n%2!=0);
?? }
?
5,可以讓i!=i嗎?
在本題中,要求你聲明一個(gè)i值,使得以下程序輸出"No i!=i":
//在此聲明i,并賦值。
if(i==i){
????? System.out.println("Yes i==i");
? }else{
????? System.out.println("No i!=i");
? }
?
??? 當(dāng)你看到這個(gè)命題的時(shí)候一定會(huì)以為我瘋了,或者Java語言瘋了。這看起來是絕對(duì)不可能的,一個(gè)數(shù)怎么可能不等于它自己呢?或許就真的是Java語言瘋了,不信請(qǐng)將i做出以下聲明,再運(yùn)行上面的代碼。
? double i=0.0/0.0;
??? 上面的代碼輸出"No i!=i",為什么會(huì)這樣呢?關(guān)鍵在0.0/0.0這個(gè)值,在IEEE 754浮點(diǎn)算術(shù)規(guī)則里保留了一個(gè)特殊的值用來表示一個(gè)不是數(shù)字的數(shù)量。這個(gè)值就是NaN("Not a Number"的縮寫),對(duì)于所有沒有良好定義的浮點(diǎn)計(jì)算都將得到這個(gè)值,比如:0.0/0.0;其實(shí)我們還可以直接使用Double.NaN來得到這個(gè)值。在IEEE 754規(guī)范里面規(guī)定NaN不等于任何值,包括它自己。所以就有了i!=i的代碼。
?
6,2.0-1.1==0.9嗎?
?考察下面的代碼:
?double a=2.0,b=1.1,c=0.9;
?if(a-b==c){
?? System.out.println("YES!");
?}else{
?? System.out.println("NO!");
?}
以上代碼輸出的結(jié)果是多少呢?你認(rèn)為是“YES!”嗎?那么,很遺憾的告訴你,不對(duì),Java語言再一次欺騙了你,以上代碼會(huì)輸出“NO!”。為什么會(huì)這樣呢?其實(shí)這是由實(shí)型數(shù)據(jù)的存儲(chǔ)方式?jīng)Q定的。我們知道實(shí)型數(shù)據(jù)在內(nèi)存空間中是近似存儲(chǔ)的,所以2.0-1.1的結(jié)果不是0.9,而是0.88888888889。所以在做實(shí)型數(shù)據(jù)是否相等的判斷時(shí)要非常的謹(jǐn)慎。一般來說,我們不建議在代碼中直接判斷兩個(gè)實(shí)型數(shù)據(jù)是否相等,如果一定要比較是否相等的話我們也采用以下方式來判斷:
? if(Math.abs(a-b)<1e-5){
???? //相等
? }else{
??? //不相等
? }
上面的代碼判斷a與b之差的絕對(duì)值是否小于一個(gè)足夠小的數(shù)字,如果是,則認(rèn)為a與b相等,否則,不相等。
posted on 2008-06-13 14:41 金家寶 閱讀(492) 評(píng)論(2) 編輯 收藏 所屬分類: Java