你真的瞭解Java中的負數嗎,你真的能弄清楚java中的多重轉型嗎,如果不是那麼清楚,那麼請看以下內容:
下面這行代碼的輸出是什麼?
1
2
3
|
public <span></span> static void
main(String[] args) { System.out.println( 0xffffffff ); } |
1
2
3
4
5
|
public static
void
main(String[] args) { byte b=- 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
|
public class
Multicast{ public static
void
main (String[] args){ System.out.println(( int )( char )( byte )- 1 ); } } |
一、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
|
int i
= c & 0xffff ; |
如果需要符號擴展,則可以如下編碼:
1
|
int i
= ( short )c; //Cast
causes sign extension |
如果在將一個byte數值b轉型爲一個char時,並且不希望有符號擴展,那麼必須使用一個位掩碼來限制它:
1
|
char c
= ( char )(b
& 0xff ); |
如果需要符號擴展,則編碼如下:
1
|
char c
= ( 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解惑》