Java解惑(一) puzzle 1–10

來源:互聯網
上載者:User

  把《Java解惑》這本書又從圖書館拿回來了,現在再次的重溫,與大三時看這本書的時候不同,我決定寫點筆記了。每天抽些時間讀些puzzle來讓愚鈍的大腦清醒一些,讀這本書的每一個puzzle的時候,感覺就像小品裡面範偉飾演的角色一樣,經常會說“原來是這麼回事呀”。但,不同的是,讀puzzle讓人更聰明,而不是被忽悠。所以決定寫個系列,利用這個周末到下周的幾天一口氣讀完吧。下面的這些都是用自己的語言來描述的,完全是自己的一些理解,只是寫下來記錄一下自己的想法,沒看過這本書的童鞋還是直接去看原版書吧。每天10個puzzle的話,那麼也需要十天的時間。雖然會浪費些時間,但總比蛋疼的在微博和各種SNS上數日子強,“親,還有XX天就世界末日了。。”。言歸正傳。

  第一章是運算式之謎,裡面主要是利用了java語言的一些標準規範,一些錯誤也是由於不規範的代碼寫法造成的。

  puzzle 1 奇數性

    public static boolean isOdd(int i) {        return i % 2 == 1;    }

  這是一個判斷奇偶的方法,有什麼不妥嗎?

   當然這個是一個不完備的方法嗎,沒有考慮到-1的情況,負數奇數模2會得到-1,所以在這個判斷中,只有正數奇數才能返回true。這裡如果用0來判斷會有更好的效果,就可以避免上面的錯誤。其實從語義的角度,0也更加的合理,我們很早以前不是就學過,奇數是不能被2整除的,而不是說“奇數是那些被2除餘1的數”。書中給出的一個好的解決方案是 return (i & 1)!= 0 ,雖然看起來比較smart但是還是不如模數看上去直觀。

 

  puzzle 2 找零時刻 

        System.out.println(2.00 - 1.10);

    這個更加的精鍊了,我也以為會列印出來的是 0.90,結果卻是0.89999999

    原因就是double無法精確的表示諸如0.1這樣的小數,說起來慚愧,我竟然忽略了這個問題。浮點的形式是1010.10101這種,同十進位類似,每一個數位都有相應的權重在裡面。只看小數點後面的數位,比如0.1表示 1/2= 0.5,0.01表示1/4= 0.25 ,0.11表示1/2+1/4=0.75諸如此類,最後只能通過延長數位來無限的接近0.1這樣的數值。如果想多瞭解這方面的知識的話,可以看下《深入理解電腦系統》這本書。Bloch給出的建議是使用java中的BigDecimal類來處理精確的浮點計算,有意思的是在看到BigDecimal類的重載構造方法BigDecimal(char[] in, int offset, int len)的時候,很有意思,這個方法實現了由一個String轉換為浮點的操作,我覺得這段代碼倒是非常值得學習一下,以前在微軟面試的時候有過類似的例子。

 

puzzle 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);    }}

    這個可以簡單的看出來,第一個MICROS_PER_DAY會溢出,不要被前面的long迷惑掉,這個long只有在運算式計算完成後賦值才會用,但是在整個運算式計算中,每一個常量都是int,整個過程也是按照int型來計算的,這樣的空間當然就不夠了。這個錯誤還是經常容易犯的。

 

puzzle 4 初級問題
public class Elementary {    public static void main(String[] args) {        System.out.println(12345 + 5432l);    }}

    這個問題實在太蛋疼,首先列印結果肯定不是你看起來的兩個十進位數字相加,但是第二個加數實際上是5432,後面的是字母l.......這是個代碼寫法引發的錯誤,所以以後Long還是大寫吧,好嗎?

 

puzzle 5十六進位的趣事
public class JoyOfHex {    public static void main(String[] args) {        System.out.println(            Long.toHexString(0x100000000L + 0xcafebabe));    }}

    又是一個簡單的加法,大部分人認為結果是0x1cafebabeL 但是值得注意的是,右邊的加數是一個十六進位的整型,十六進位的常量是帶有符號的,即最高位被置位的話就表示負數。所以0xcafebabe是一個負數,相加的時候首先要進行擴充,這樣就成了0xffffffffcafebabe。

 

puzzle 6多重轉換
public class Multicast {    public static void main(String[] args) {        System.out.println((int) (char) (byte) -1);    }}

     這個例子講的是將-1這個整型經過多重轉換,最後列印出來的是什嗎?首先經過前面那麼多puzzles,我們敏感的先看一下三個資料類型的佔位int 32 char 16 byte8,再根據轉換順序,可以看出我們需要先把32位的-1截斷位8位,然後再擴充為16位,再擴充為32位。首先-1使用二進位補碼錶示的,即1111...1保留最後8位後,還是-1.char是無符號,這就變成了65535.而此時char在擴充為有符號的int,前面依舊補零,所以最後我們看到就是65535.

     書中給了一種不錯的建議,當涉及到符號擴充的時候可以與一個較寬的資料類型相與,比如byte轉換為char時,如果不希望符號擴充可以採用下面的操作:

     char c = (char)(b & 0xff);   這樣就不會發生符號擴充了,相通道理大家都明白。

 

puzzle 7 互換內容
public class CleverSwap {    public static void main(String[] args) {        int x = 1984;        int y = 2001;        x ^= y ^= x ^= y;        System.out.println("x = " + x + "; y = " + y);    }}

     看到這個代碼就很不喜歡,寫法很差,我想估計也只有想說明某個例子的時候才會這麼寫。首先背景是,我想大家都知道一個小tip 就是不使用臨時變數來交換兩個數。基本有兩個方式比如第一種是 : a = a+b;b=a-b;a=a-b;第二種就是 a = a ^ b; b = a ^ b; a = a ^ b;後者是比較有名的,利用了一個數和自身異或為0,而任何數字和0異或還是自己。所以就有了上面的這種昔寫法。。當然上面的代碼是得不到正確的結果的,Bloch將上面的x ^= y ^= x ^= y分解了,我覺得這樣就很明白了:首先明確兩點,一是操作符從左向右求值,二是x^=y的過程是先取x的值然後,求x ^ y ,再將值賦值給x.

     所以分解一下上面的操作是(來自書中原文):

  puzzle 8 Dos Equis
public class DosEquis {    public static void main(String[] args) {        char x = 'X';        int i = 0;        System.out.print(true  ? x : 0);        System.out.print(false ? i : x);     }}

     我表示盯著這個運算式看了很久都沒有發現問題,原因是我只把 ?condition x:y當成了一種控制結構了,而忽略了這其實是一個運算式,而運算式的特點是他是有值的,這個值得類型是什嗎?相信到這裡就可以發現問題了。這其實是一個混合類型的計算,冒號左右的兩個資料類型不一致。書中介紹了一個規則,就是:

  • 如果第二個和第三個運算元具有相同的類型,那麼它就是條件運算式的類型。換句話說,你可以通過繞過混合類型的計算來避免大麻煩。
  • 如果一個運算元的類型是T,T 表示byte、short 或char,而另一個運算元是一個int 類型的常量運算式,它的值是可以用類型T 表示的,那麼條

件運算式的類型就是T。

  •  否則,將對運算元類型運用位元字提升,而條件運算式的類型就是第二個和第三個運算元被提升之後的類型。

     所以第二個運算式中,i是變數,所以x也被提升為int了,就有了這樣的結果,這個puzzle還是非常不錯的。

 

puzzle 9半斤 puzzl 10 八兩
public class Tweedledum {    public static void main(String[] args) {        // Put your declarations for x and i here        x += i;     // Must be LEGAL        x = x + i;  // Must be ILLEGAL    }}

     這個例子昨天和室友賴神一起吃飯還聊到了,很有意思的兩個puzzle放在一起寫,要求給出x和i的聲明,第一個就是需要 x += i合法,而x = x+i不合法。這個我還有一點印象,記得是和資料類型的寬窄有關,即你不能將一個寬資料類型賦給一個比他要窄的。 所以如果x是short 而 i是int ,第二個就不合法了。現在看複合賦值運算式為什麼合法。。java設計事其然。“複合賦值運算式自動地將它們所執行的計算的結果轉型為其左側變數的類型”,這個時候如果左側的更窄則隱式的進行截斷。所以複合賦值運算式是危險的!

    再看另一個八兩,這個我沒有想到, 使得x += i不合法,而x = x+i合法。這裡涉及到了一個知識點是複合賦值操作符只適用於基本類型,或者封裝了基本類型的類,比如Integer。String除外,當左邊是String時,右邊可以是任何類型。這是不難想到一個使得x += i不合法的例子,那就是將x定義為一個Object就Ok了。而在賦值操作中,Object作為一個引用則可以指向其他類型。所以方法就是 x是Object而i是String。

 

    以上就是Chapter 2的10個puzzle,主要是和運算式的值有關,其中多次涉及到了不同寬窄資料類型之間的轉換問題,資料類型佔位不一給開發造成了一些麻煩,但是出於效率的考慮,似乎也有他的價值,這一點不敢多說,記得有本書中寫道盡量不要用float,因為他的效率並不比double高多少,但是後者卻又更高的精度。其次涉及到了一些特殊運算式容易造成的陷阱,雖然感覺有點過細了,但是還是能夠讓我們開闊一下視野,起碼非今後調試BUG也很有協助。

   下一章是字元puzzle,字串是最有意思的話題,今天到這。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.