Java語言表達(dá)式的五個(gè)謎題是什么

這篇“Java語言表達(dá)式的五個(gè)謎題是什么”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java語言表達(dá)式的五個(gè)謎題是什么”文章吧。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、網(wǎng)絡(luò)空間、營銷軟件、網(wǎng)站建設(shè)、大冶網(wǎng)站維護(hù)、網(wǎng)站推廣。

Java語言表達(dá)式的五個(gè)謎題是什么

謎題一:奇數(shù)性

下面的方法意圖確定它那唯一的參數(shù)是否是一個(gè)奇數(shù)。這個(gè)方法能夠正確運(yùn)轉(zhuǎn)嗎?

public static boolean isOdd(int i){ 
returni%2==1:
}

奇數(shù)可以被定義為被2整除余數(shù)為1的整數(shù)。表達(dá)式i%2計(jì)算的是i整除2時(shí)所產(chǎn)生的余數(shù),因此看起來這個(gè)程序應(yīng)該能夠正確運(yùn)轉(zhuǎn)。遺憾的是,它不能;它在四分之一的時(shí)間里返回的都是錯(cuò)誤的答案。

為什么是四分之一?因?yàn)樵谒械膇nt數(shù)值中,有一半都是負(fù)數(shù),而isOdd方法對(duì)于對(duì)所有負(fù)奇數(shù)的判斷都會(huì)失敗。在任何負(fù)整數(shù)上調(diào)用該方法都回返回false,不管該整數(shù)是偶數(shù)還是奇數(shù)。這是Java對(duì)取余操作符(%)的定義所產(chǎn)生的后果。該操作符被定義為對(duì)于所有的int數(shù)值a和所有的非零int數(shù)值b,都滿足下面的恒等式:

(a/b)*b+(a%b)==a

Java語言表達(dá)式的五個(gè)謎題是什么

換句話說,如果你用b整除a,將商乘以b,然后加上余數(shù),那么你就得到了最初的值a。該恒等式具有正確的含義,但是當(dāng)與Java的截尾整數(shù)整除操作符相結(jié)合時(shí),它就意味著:當(dāng)取余操作返回一個(gè)非零的結(jié)果時(shí),它與左操作數(shù)具有相同的正負(fù)符號(hào)。

當(dāng)i是一個(gè)負(fù)奇數(shù)時(shí),i%2等于-1而不是1,因此isOdd方法將錯(cuò)誤地返回false。為了防止這種意外,請(qǐng)測(cè)試你的方法在為每一個(gè)數(shù)值型參數(shù)傳遞負(fù)數(shù)、零和正數(shù)數(shù)值時(shí),其行為是否正確。這個(gè)問題很容易訂正。只需將i%2與0而不是與1比較,并且反轉(zhuǎn)比較的含義即可:

public static boolean isOdd(inti){ 
returni%2!=0;
}

如果你正在在一個(gè)性能臨界(performance-critical)環(huán)境中使用isOdd方法,那么用位操作符AND(&)來替代取余操作符會(huì)顯得更好:

public static boolean isOdd(inti){ 
return(i&1)!=0;
}

總之,無論你何時(shí)使用到了取余操作符,都要考慮到操作數(shù)和結(jié)果的符號(hào)。該操作符的行為在其操作數(shù)非負(fù)時(shí)是一目了然的,但是當(dāng)一個(gè)或兩個(gè)操作數(shù)都是負(fù)數(shù)時(shí),它的行為就不那么顯而易見

了。

謎題二:找零時(shí)刻

請(qǐng)考慮下面這段話所描述的問題:

Tom在一家汽車配件商店購買了一個(gè)價(jià)值$1.10的火花塞,但是他錢包中都是兩美元一張的鈔票。如果他用一張兩美元的鈔票支付這個(gè)火花塞,那么應(yīng)該找給他多少零錢呢?

下面是一個(gè)試圖解決上述問題的程序,它會(huì)打印出什么呢?

public class Change{
public static void main(String args[]){ 
Systemoutprintln(2.00-1.10);
}
}

你可能會(huì)很天真地期望該程序能夠打印出0.90但是它如何才能知道你想要打印小數(shù)點(diǎn)后兩位小數(shù)呢?

如果你對(duì)在DoubletoString文檔中所設(shè)定的將 double類型的值轉(zhuǎn)換為字符串的規(guī)則有所了解你就會(huì)知道該程序打印出來的小數(shù),是足以將 double類型的值與最靠近它的臨近值區(qū)分出來的最短的小數(shù),它在小數(shù)點(diǎn)之前和之后都至少有一位。因此,看起來,該程序應(yīng)該打印0.9是合理的。

這么分析可能顯得很合理,但是并不正確。如果你運(yùn)行該程序,你就會(huì)發(fā)現(xiàn)它打印的是:

0.8999999999999999

Java語言表達(dá)式的五個(gè)謎題是什么

問題在于1.1這個(gè)數(shù)字不能被精確表示成為一個(gè) double,因此它被表示成為最接近它的double值。該程序從2中減去的就是這個(gè)值。遺憾的是,這個(gè)計(jì)算的結(jié)果并不是最接近0.9的double值。表示結(jié)果的double值的最短表示就是你所看到的打印出來的那個(gè)可惡的數(shù)字。

更一般地說,問題在于并不是所有的小數(shù)都可以用二進(jìn)制浮點(diǎn)數(shù)來精確表示的。

如果你正在用的是JDK5.0或更新的版本,那么你可能會(huì)受其誘惑,通過使用printf工具來設(shè)置輸出精度的方訂正該程序:

//拙劣的解決方案-仍舊是使用二進(jìn)制浮點(diǎn)數(shù)

System.out.printf("%.2f%n",2.00-1.10);

這條語句打印的是正確的結(jié)果,但是這并不表示它就是對(duì)底層問題的通用解決方案:它使用的仍日是二進(jìn)制浮點(diǎn)數(shù)的double運(yùn)算。浮點(diǎn)運(yùn)算在一個(gè)范圍很廣的值域上提供了很好的近似,但是它通常不能產(chǎn)生精確的結(jié)果。二進(jìn)制浮點(diǎn)對(duì)于貨幣計(jì)算是非常不適合的,因?yàn)樗豢赡軐?.1-或者10的其它任何次負(fù)冪--精確表示為一個(gè)長度有限的二進(jìn)制小數(shù)解決該問題的一種方式是使用某種整數(shù)類型,例如int或long,并且以分為單位來執(zhí)行計(jì)算。如果你采納了此路線,請(qǐng)確保該整數(shù)類型大到足夠表示在程序中你將要用到的所有值。對(duì)這里舉例的謎題來說,int就足夠了。下面是我們用int類型來以分為單位表示貨幣值后重寫的println語句。這個(gè)版本將打印出正確答案90分:

Systemoutprintln((200-110)+"cents")

解決該問題的另一種方式是使用執(zhí)行精確小數(shù)運(yùn)算的BigDecimal。它還可以通過JDBC與SQL DECIMAL類型進(jìn)行互操作。這里要告誡你一點(diǎn):一定要用BigDecimal(String)構(gòu)造器,而千萬不要用BigDecimal(double)。后一個(gè)構(gòu)造器將用它的參數(shù)的精確”值來創(chuàng)建一個(gè)實(shí)例:new BigDecimal(1)將返回一個(gè)表示0100000000000000055511151231257827021181583404541015625BigDecimal。通過正確使用BigDecimal,程序就可以打印出我們所期望的結(jié)果0.90:

import java.math.BigDecimal; 
public class Changel {
public static void main(String args[]){
System.out.println(newBigDecimal(2.00")
subtract(new BigDecimal("1.10")));
}
}

這個(gè)版本并不是十分地完美,因?yàn)镴ava并沒有為 BigDecimal提供任何語言上的支持。使用

BigDecimal的計(jì)算很有可能比那些使用原始類型的計(jì)算要慢一些,對(duì)某些大量使用小數(shù)計(jì)算的程序來說,這可能會(huì)成為問題,而對(duì)大多數(shù)程序來說,這顯得一點(diǎn)也不重要。

總之,在需要精確答案的地方,要避免使用 float和double;對(duì)于貨幣計(jì)算,要使用int、long或BigDecimal。對(duì)于語言設(shè)計(jì)者來說,應(yīng)該考慮對(duì)小數(shù)運(yùn)算提供語言支持。一種方式是提供對(duì)操作符重載的有限支持,以使得運(yùn)算符可以被塑造為能夠?qū)?shù)值引用類型起作用,例如BigDecimal。另一種方式是提供原始的小數(shù)類型,就像COBOL與PL/I所作的一樣。

謎題三:長整數(shù)

這個(gè)謎題之所以被稱為長整除是因?yàn)樗婕暗某绦蚴怯嘘P(guān)兩個(gè)long型數(shù)值整除的。被除數(shù)表示的是一天里的微秒數(shù);而除數(shù)表示的是一天里的毫秒數(shù)。這個(gè)程序會(huì)打印出什么呢?

public class Longpision{
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;
Systemoutprintln(MICROS PER DAY/ MILLIS PER DAY);
}
}

這個(gè)謎題看起來相當(dāng)直觀。每天的毫秒數(shù)和每天的微秒數(shù)都是常量。為清楚起見,它們都被表示成積的形式。每天的微秒數(shù)是(24小時(shí)/天*60分鐘/小時(shí)*60秒/分鐘*1000毫秒/秒*1000微秒/毫秒)。而每天的毫秒數(shù)的不同之處只是少了最后一個(gè)因子1000。當(dāng)你用每天的毫秒數(shù)來整除每天的微秒數(shù)時(shí),除數(shù)中所有的因子都被約掉了,只剩下1000,這正是每毫秒包含的微秒數(shù)。

除數(shù)和被除數(shù)都是long類型的,long類型大到了可以很容易地保存這兩個(gè)乘積而不產(chǎn)生溢出。因此,看起來程序打印的必定是1000。遺憾的是,它打印的是5。這里到底發(fā)生了什么呢?

問題在于常數(shù)MICROS PER DAY的計(jì)算確實(shí)”溢出了。盡管計(jì)算的結(jié)果適合放入long中,并且其空間還有富余,但是這個(gè)結(jié)果并不適合放入 int中。這個(gè)計(jì)算完全是以int運(yùn)算來執(zhí)行的,并且只有在運(yùn)算完成之后,其結(jié)果才被提升到long,而此時(shí)已經(jīng)太遲了:計(jì)算已經(jīng)溢出了,它返回的是一個(gè)小了200倍的數(shù)值。從int提升到 long是一種拓寬原始類型轉(zhuǎn)換(widening primitive conversion),它保留了(不正確的)數(shù)值。這個(gè)值之后被MILLIS PER DAY整除,而MILLIS PER DAY的計(jì)算是正確的,因?yàn)樗m合int運(yùn)算。這樣整除的結(jié)果就得到了5。

那么為什么計(jì)算會(huì)是以int運(yùn)算來執(zhí)行的呢?為所有乘在一起的因子都是int數(shù)值。當(dāng)你將兩個(gè)int數(shù)值相乘時(shí),你將得到另一個(gè)int數(shù)值。Java不具有目標(biāo)確定類型的特性,這是一種語言特性,其含義是指存儲(chǔ)結(jié)果的變量的類型會(huì)影響到計(jì)算所使用的類型。

通過使用long常量來替代int常量作為每一個(gè)乘積的第一個(gè)因子,我們就可以很容易地訂正這個(gè)程序。這樣做可以強(qiáng)制表達(dá)式中所有的后續(xù)計(jì)算都用long運(yùn)作來完成。盡管這么做只在MICROS PER DAY表達(dá)式中是必需的,但是在兩個(gè)乘積中都這么做是一種很好的方式。相似地,使用long作為乘積的“第一個(gè)”數(shù)值也并不總是必需的,但是這么做也是一種很好的形式。在兩個(gè)計(jì)算中都以long數(shù)值開始可以很清楚地表明它們都不會(huì)溢出。下面的程序?qū)⒋蛴〕鑫覀兯谕?000:

Java語言表達(dá)式的五個(gè)謎題是什么

public class Longpision{

public static void main(String args[)

final long MICROS PER DAY=24L*60*60*1000*1000:

final long MILLIS PER DAY=24L*60*60*1000;

SystemoutprintlnMICROS PER DAY MILLIS PER DAY);

}

}

這個(gè)教訓(xùn)很簡單:當(dāng)你在操作很大的數(shù)字時(shí),千萬要提防溢出--它可是一個(gè)緘默殺手。即使用來保存結(jié)果的變量已顯得足夠大,也并不意味著要產(chǎn)生結(jié)果的計(jì)算具有正確的類型。當(dāng)你拿不準(zhǔn)時(shí),就使用long運(yùn)算來執(zhí)行整個(gè)計(jì)算。

語言設(shè)計(jì)者從中可以吸取的教訓(xùn)是:也許降低默溢出產(chǎn)生的可能性確實(shí)是值得做的一件事。這可以通過對(duì)不會(huì)產(chǎn)生緘默溢出的運(yùn)算提供支持來實(shí)現(xiàn)。程序可以拋出一個(gè)異常而不是直接溢出。就像Ada所作的那樣,或者它們可以在需要的時(shí)候自動(dòng)地切換到一個(gè)更大的內(nèi)部表示上以防止溢出,就像Lisp所作的那樣。這兩種方式都可能會(huì)遭受與其相關(guān)的性能方面的損失。降低緘默溢出的另一種方式是支持目標(biāo)確定類型,但是這么做會(huì)顯著地增加類型系統(tǒng)的復(fù)雜度。

謎題四:初級(jí)問題

得啦,前面那個(gè)謎題是有點(diǎn)棘手,但它是有關(guān)整除的,每個(gè)人都知道整除是很麻煩的。那么下面的程序只涉及加法,它又會(huì)打印出什么呢?

public class Elementary{

public static void main(String]args) {

Systemoutprintln(12345+54321);

}

}

從表面上看,這像是一個(gè)很簡單的謎題--簡單到不需要紙和筆你就可以解決它。加號(hào)的左操作數(shù)的各個(gè)位是從1到5升序排列的,而右操作數(shù)是降序排列的。因此,相應(yīng)各位的和仍然是常數(shù),程序必定打印66666。對(duì)于這樣的分析,只有一個(gè)問題:當(dāng)你運(yùn)行該程序時(shí),它打印出的是17777。難道是Java對(duì)打印這樣的非常數(shù)字抱有偏見嗎?不知怎么的,這看起來并不像是一個(gè)合理的解釋。

事物往往有別于它的表象。就以這個(gè)問題為例,它并沒有打印出我們想要的輸出。請(qǐng)仔細(xì)觀察+操作符的兩個(gè)操作數(shù),我們是將一個(gè)int類型的12345加到了long類型的54321上。請(qǐng)注意左操作數(shù)開頭的數(shù)字1和右操作數(shù)結(jié)尾的小寫字母1之間的細(xì)微差異。數(shù)字1的水平筆劃(稱為“臂(arm)”)和垂直筆劃(稱為“莖(stem)”)之間是一個(gè)銳角,而與此相對(duì)照的是,小寫字母l的臂和莖之間是一個(gè)直角。

Java語言表達(dá)式的五個(gè)謎題是什么

在你大喊“惡心!”之前,你應(yīng)該注意到這個(gè)問題確實(shí)已經(jīng)引起了混亂,這里確實(shí)有一個(gè)教訓(xùn):在 long型字面常量中,一定要用大寫的L,千萬不要用小寫的1。這樣就可以完全掐斷這個(gè)謎題所產(chǎn)生的混亂的源頭。

System.out.println(12345+5432L);

相類似的,要避免使用單獨(dú)的一個(gè)1字母作為變量名。例如,我們很難通過觀察下面的代碼段來判斷它到底是打印出列表1還是數(shù)字1。

List l=new ArrayList<String>() ;

l.add("Foo");

System.outprintln(1);

Java語言表達(dá)式的五個(gè)謎題是什么

總之,小寫字母l和數(shù)字1在大多數(shù)打字機(jī)字體中都是幾乎一樣的。為避免你的程序的讀者對(duì)二者產(chǎn)生混淆,千萬不要使用小寫的1來作為long型字面常量的結(jié)尾或是作為變量名。Java從C編程語言中繼承良多,包括long型字面常量的語法。也許當(dāng)初允許用小寫的1來編寫long型字面常量本身就是一個(gè)錯(cuò)誤。

謎題五:十六進(jìn)制的趣事

下面的程序是對(duì)兩個(gè)十六進(jìn)制(hex)字面常量進(jìn)行相加,然后打印出十六進(jìn)制的結(jié)果。這個(gè)程序會(huì)打印出什么呢?

public class JoyOfHex{

public static void main(String[] args){ 

System.out.println(

Long.toHexString(0x100000000L+0xcafebabe));

}

}

看起來很明顯,該程序應(yīng)該打印出1cafebabe。畢竟,這確實(shí)就是十六進(jìn)制數(shù)字10000000016與 cafebabe16的和。該程序使用的是long型運(yùn)算,它可以支持16位十六進(jìn)制數(shù),因此運(yùn)算溢出是不可能的。

然而,如果你運(yùn)行該程序,你就會(huì)發(fā)現(xiàn)它打印出來的是cafebabe,并沒有任何前導(dǎo)的1。這個(gè)輸出表示的是正確結(jié)果的低32位,但是不知何故第33位丟失了。

看起來程序好像執(zhí)行的是int型運(yùn)算而不是long型運(yùn)算,或者是忘了加第一個(gè)操作數(shù)。這里到底發(fā)生了什么呢?

十進(jìn)制字面常量具有一個(gè)很好的屬性,即所有的十進(jìn)制字面常量都是正的,而十六進(jìn)制和八進(jìn)制字面常量并不具備這個(gè)屬性。要想書寫一個(gè)負(fù)的十進(jìn)制常量,可以使用一元取反操作符(-)連接一個(gè)十進(jìn)制字面常量。以這種方式,你可以用十進(jìn)制來書寫任何int或long型的數(shù)值,不管它是正的還是負(fù)的,并且負(fù)的十進(jìn)制常數(shù)可以很明確地用一個(gè)減號(hào)符號(hào)來標(biāo)識(shí)。但是十六進(jìn)制和八進(jìn)制字面常量并不是這么回事,它們可以具有正的以及負(fù)的數(shù)值。如果十六進(jìn)制和八進(jìn)制字面常量的最高位被置位了,那么它們就是負(fù)數(shù)。在這個(gè)程序中,數(shù)字Oxcafebabe是一個(gè)int常量,它的最高位被置位了,所以它是一個(gè)負(fù)數(shù)。它等于十進(jìn)制數(shù)值-889275714。

Java語言表達(dá)式的五個(gè)謎題是什么

該程序執(zhí)行的這個(gè)加法是一種“混合類型的計(jì)算(mixed-type computation)左操作數(shù)是long類型的,而右操作數(shù)是int類型的。為了執(zhí)行該計(jì)算,Java將int類型的數(shù)值用拓寬原始類型轉(zhuǎn)換提升為一個(gè)long類型,然后對(duì)兩個(gè)long類型數(shù)值相加。因?yàn)閕nt是一個(gè)有符號(hào)的整數(shù)類型,所以這個(gè)轉(zhuǎn)換執(zhí)行的是符合擴(kuò)展:它將負(fù)的int類型的數(shù)值提升為一個(gè)在數(shù)值上相等的long類型數(shù)值。這個(gè)加法的右操作數(shù)0xcafebabe被提升為了long類型的數(shù)值0xffffffffcafebabeL。這個(gè)數(shù)值之后被加到了左操作數(shù)0x100000000L上。當(dāng)作為int類型來被審視時(shí),經(jīng)過符號(hào)擴(kuò)展之后的右操作數(shù)的高32位是-1,而左操作數(shù)的高32位是1,將這兩個(gè)數(shù)相加就得到了0,這也就解釋為什么在程序輸出中前導(dǎo)1丟失了。下面所示是用手寫的加法實(shí)現(xiàn)。(在加法上面的數(shù)字是進(jìn)位。)

1111111

0xffffffffcafebabeL

+0x0000000100000000L

0x00000000cafebabeL

訂正該程序非常簡單,只需用一個(gè)long十六進(jìn)制字面常量來表示右操作數(shù)即可。這就可以避免了具有破壞力的符號(hào)擴(kuò)展,并且程序也就可以打印出我們所期望的結(jié)果1cafebabe:

public class JoyOfHex{

public static void main(String[] args){ 

System.outprintln(

LongtoHexString(0x100000000L+0xcafebabeL));

}

}

這個(gè)謎題給我們的教訓(xùn)是:混合類型的計(jì)算可能會(huì)產(chǎn)生混淆,尤其是十六進(jìn)制和八進(jìn)制字面常量無需顯式的減號(hào)符號(hào)就可以表示負(fù)的數(shù)值。為了避免這種窘境,通常最好是避免混合類型的計(jì)算。對(duì)于語言的設(shè)計(jì)者們來說,應(yīng)該考慮支持無符號(hào)的整數(shù)類型,從而根除符號(hào)擴(kuò)展的可能性。可能會(huì)有這樣的爭辯:負(fù)的十六進(jìn)制和八進(jìn)制字面常量應(yīng)該被禁用,但是這可能會(huì)挫傷程序員,他們經(jīng)常使用十六進(jìn)制字面常量來表示那些符號(hào)沒有任何重要含義的數(shù)值。

以上就是關(guān)于“Java語言表達(dá)式的五個(gè)謎題是什么”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

新聞名稱:Java語言表達(dá)式的五個(gè)謎題是什么
當(dāng)前URL:http://muchs.cn/article32/ishopc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、App設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)公司電子商務(wù)自適應(yīng)網(wǎng)站企業(yè)建站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)