java基本數據類型的多重轉型

你真的瞭解Java中的負數嗎,你真的能弄清楚java中的多重轉型嗎,如果不是那麼清楚,那麼請看以下內容:

下面這行代碼的輸出是什麼?

?
1
2
3
public<span></span>staticvoid main(String[] args) {
  System.out.println(0xffffffff);
}
下面兩行代碼的輸出相同嗎?
?
1
2
3
4
5
publicstatic void main(String[] args) {
  byteb=-1;
  System.out.println((int)(char)b);
  System.out.println((int)(char)(b & 0xff));
}

請嘗試在Eclipse中運行上面的兩個代碼片段,如果你對輸出結果感到很驚訝,請繼續往下讀...

正如你所看到的:
第1個代碼片段的運行結果是:-1
第2個代碼片段的運行結果是:65535和255

上面的兩個代碼片段來源於《Java解惑》的第6個小問題“多重轉型”,原題目內容如下:

?
1
2
3
4
5
publicclass Multicast{
  publicstatic void main (String[] args){
    System.out.println((int)(char)(byte)-1);
  }
}
上面的代碼中連續進行了3次類型轉換,最後的結果會回到-1嗎?答案當然是不會,它輸出的結果是65535。下面我爲大家整理了相關的基礎知識,相信大家讀完後應該就知道其中的原因了。


一、Java中如何編碼負數?

    Java採用”2的補碼“(Two's Complement)編碼負數,它是一種數值的編碼方法,要分二步完成:第一步,每一個二進制位都取相反值,0變成1,1變成0。比如,+8的二進制編碼是00001000,取反後就是11110111。第二步,將上一步得到的值加1。11110111就變成11111000。所以,00001000的2的補碼就是11111000。也就是說,-8在計算機(8位機)中就是用11111000表示。關於“2的補碼”的詳細信息,請參考阮一峯的博文《關於2的補碼》,博文地址附在本文的參考部分。

二、什麼是符號擴展(Sign Extension)?

    符號擴展(Sign Extension)用於在數值類型轉換時擴展二進制位的長度,以保證轉換後的數值和原數值的符號(正或負)和大小相同,一般用於較窄的類型(如byte)向較寬的類型(如int)轉換。擴展二進制位長度指的是,在原數值的二進制位左邊補齊若干個符號位(0表示正,1表示負)。

    舉例來說,如果用6個bit表示十進制數10,二進制碼爲"00 1010",如果將它進行符號擴展爲16bits長度,結果是"0000 0000 0000 1010",即在左邊補上10個0(因爲10是正數,符號爲0),符號擴展前後數值的大小和符號都保持不變;如果用10bits表示十進制數-15,使用“2的補碼”編碼後,二進制碼爲"11 1111 0001",如果將它進行符號擴展爲16bits,結果是"1111 1111 1111 0001",即在左邊補上6個1(因爲-15是負數,符號爲1),符號擴展前後數值的大小和符號都保持不變

三、Java類型轉換規則

1. Java中整型字面量   

 Java中int型字面量的書寫方式有以下幾種:

    - 十進制方式,直接書寫十進制數字

    - 八進制方式,格式以0打頭,例如012表示十進制10

    - 十六進制方式,格式爲0x打頭,例如0xff表示十進制255

 需要注意的是,在Java中012和0xff返回的都是int型數據,即長度是32位。

2. Java的數值類型轉換規則

    這個規則是《Java解惑》總結的:如果最初的數值類型是有符號的,那麼就執行符號擴展;如果是char類型,那麼不管它要被轉換成什麼類型,都執行零擴展。還有另外一條規則也需要記住,如果目標類型的長度小於源類型的長度,則直接截取目標類型的長度。例如將int型轉換成byte型,直接截取int型的右邊8位。

四、解析“多重轉型”問題

  連續三次類型轉換的表達式如下:

?
1
(int)(char)(byte)-1

1. int(32位) -> byte(8位)

  -1是int型的字面量,根據“2的補碼”編碼規則,編碼結果爲0xffffffff,即32位全部置1.轉換成byte類型時,直接截取最後8位,所以byte結果爲0xff,對應的十進制值是-1.

2. byte(8位) -> char(16位)

  由於byte是有符號類型,所以在轉換成char型(16位)時需要進行符號擴展,即在0xff左邊連續補上8個1(1是0xff的符號位),結果是0xffff。由於char是無符號類型,所以0xffff表示的十進制數是65535。

3. char(16位) -> int(32位)

  由於char是無符號類型,轉換成int型時進行零擴展,即在0xffff左邊連續補上16個0,結果是0x0000ffff,對應的十進制數是65535。

五、幾個轉型的例子

  在進行類型轉換時,一定要了解表達式的含義,不能光靠感覺。最好的方法是將你的意圖明確表達出來。

  在將一個char型數值c轉型爲一個寬度更寬的類型時,並且不希望有符號擴展,可以如下編碼:

?
1
inti = c & 0xffff;
  上文曾提到過,0xffff是int型字面量,所以在進行&操作之前,編譯器會自動將c轉型成int型,即在c的二進制編碼前添加16個0,然後再和0xffff進行&操作,所表達的意圖是強制將前16置0,後16位保持不變。雖然這個操作不是必須的,但是明確表達了不進行符號擴展的意圖。

如果需要符號擴展,則可以如下編碼:

?
1
inti = (short)c;//Cast causes sign extension
  首先將c轉換成short類型,它和char是 等寬度的,並且是有符號類型,再將short類型轉換成int類型時,會自動進行符號擴展,即如果short爲負數,則在左邊補上16個1,否則補上16個0.

  如果在將一個byte數值b轉型爲一個char時,並且不希望有符號擴展,那麼必須使用一個位掩碼來限制它:

?
1
charc = (char)(b & 0xff);
  (b & 0xff)的結果是32位的int類型,前24被強制置0,後8位保持不變,然後轉換成char型時,直接截取後16位。這樣不管b是正數還是負數,轉換成char時,都相當於是在左邊補上8個0,即進行零擴展而不是符號擴展。

  如果需要符號擴展,則編碼如下:

?
1
charc = (char)b;//Sign extension is performed

此時爲了明確表達需要符號擴展的意圖,註釋是必須的。

六、小結

    實際上在數值類型轉換時,只有當遇到負數時纔會出現問題,根本原因就是Java中的負數不是採用直觀的方式進行編碼,而是採用“2的補碼”方式,這樣的好處是加法和減法操作可以同時使用加法電路完成,但是在開發時卻會遇到很多奇怪的問題,例如(byte)128的結果是-128,即一個大的正數,截斷後卻變成了負數。3.2節中引用了一些轉型規則,應用這些規則可以很容地解決常見的轉型問題。

七、參考引用

1. 阮一峯-關於2的補碼  
http://www.ruanyifeng.com/blog/2009/08/twos_complement.html

2. wikipedia-Sign extension
http://en.wikipedia.org/wiki/Sign_extension

3. Joshua Bloch, 陳昊鵬譯 - 《Java解惑》

發佈了128 篇原創文章 · 獲贊 36 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章