(轉) Java中的負數及基本類型的轉型詳解


(轉) https://my.oschina.net/joymufeng/blog/139952

面這行代碼的輸出是什么?

下面兩行代碼的輸出相同嗎?

    

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

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

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

public class Multicast{
  public static 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位。

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

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

(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轉型為一個寬度更寬的類型時,並且不希望有符號擴展,可以如下編碼:

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

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

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

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

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

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

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
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM