java中的基本數據類型的轉換

本文參考了以下兩篇文章:html

https://my.oschina.net/joymufeng/blog/139952java

http://www.cnblogs.com/lwbqqyumidi/p/3700164.html數組

Java中,常常能夠遇到類型轉換的場景,從變量的定義到複製、數值變量的計算到方法的參數傳遞、基類與派生類間的造型等,隨處可見類型轉換的身影。Java中的類型轉換在Java編碼中具備重要的做用。
首先,來了解下數據類型的基本理解:數據是用來描述數據的種類,包括其值和基於其值基礎上的可進行的操做集合。jvm

Java中數據類型主要分爲兩大類:基本數據類型和引用數據類型。
基本數據類型共有8種,分別是:布爾型boolean, 字符型char和數值型byte/short/int/long/float/double。因爲字符型char所表示的單個字符與Ascii碼中相應整形對應,所以,有時也將其劃分到數值型中。引用類型具體可分爲:數組、類和接口。所以java中類型的轉化分爲基本數據類型的轉換和引用數據類型的轉換,本文將針對基本數據類型的轉換進行總結.編輯器

1.基本數據類型的類型轉換

數據類型 所佔字節
boolean 未定
byte 1字節
char 2字節
short 2字節
int 4字節
long 8字節
float 4字節
double 8字節

從上表能夠看出java中各類數據類型所佔空間的大小. 在java中整數的默認數據類型是int, 例如數字4, 小數的默認數字類型是double, 例如3.12. 當float a = 3.12時會報錯, 由於3.12的默認數據類型是double, 咱們須要使用以下的賦值方法:編碼

第一種方法在3.12後面加了一個F, 告訴編譯器這是一個float的數. 第二種方法對3.12進行了強制的類型轉換. 接下來咱們仔細分析一下java中的類型轉換問題..net

基本數據類型中,布爾類型boolean佔有一個字節,因爲其自己所代碼的特殊含義,boolean類型與其餘基本類型不能進行類型的轉換(既不能進行自動類型的提高,也不能強制類型轉換), 不然,將編譯出錯3d

 

a. 基本數據類型中類型的自動提高htm

數值類型在內存中直接存儲其自己的值,對於不一樣的數值類型,內存中會分配相應的大小去存儲。如:byte類型的變量佔用8位,int類型變量佔用32位等。相應的,不一樣的數值類型會有與其存儲空間相匹配的取值範圍。具體以下所示:blog

 

圖中依次表示了各數值類型的字節數和相應的取值範圍。在Java中,整數類型(byte/short/int/long)中,對於未聲明數據類型的整形,其默認類型爲int型。在浮點類型(float/double)中,對於未聲明數據類型的浮點型,默認爲double型。

看下面的例子

  

 

是否是有點奇怪?按照上面的思路去理解,將一個int型的1000賦給一個byte型的變量a,提示出錯,可是最後一句:將一個int型的3賦給一個byte型的變量c,竟然編譯正確,這是爲何呢?

 緣由在於:jvm在編譯過程當中,對於默認爲int類型的數值時,當賦給一個比int型數值範圍小的數值類型變量(在此統一稱爲數值類型k,k能夠是byte/char/short類型),會進行判斷,若是此int型數值超過數值類型k,那麼會直接編譯出錯。由於你將一個超過了範圍的數值賦給類型爲k的變量,k裝不下嘛,你有沒有進行強制類型轉換,固然報錯了。可是若是此int型數值尚在數值類型k範圍內,jvm會自定進行一次隱式類型轉換,將此int型數值轉換成類型k。如圖中的虛線箭頭。這一點有點特別,須要稍微注意下。

另外在IDEA中, 類型的判斷會在寫程序時由編輯器幫你作判斷, 而不須要到編譯的時候由編譯器來作判斷, 這也是IDEA的一個優勢.

在其餘狀況下,當將一個數值範圍小的類型賦給一個數值範圍大的數值型變量,jvm在編譯過程當中俊將此數值的類型進行了自動提高。在數值類型的自動類型提高過程當中,數值精度至少不該該下降(整型保持不變,float->double精度將變高)。

 

 

如上:定義long類型的a變量時,將編譯出錯,緣由在於11111111111默認是int類型,同時int類型的數值範圍是-2^31 ~ 2^31-1,所以,11111111111已經超過此範圍內的最大值,故而其自身已經編譯出錯,更談不上賦值給long型變量a了。

此時,若想正確賦值,改變11111111111自身默認的類型便可,直接改爲11111111111L便可將其自身類型定義爲long型。此時再賦值編譯正確。

將值爲10的int型變量 z 賦值給long型變量q,按照上文所述,此時直接發生了自動類型提高, 編譯正確。

接下來,還有一個地方須要注意的是:char型其自己是unsigned型,同時具備兩個字節,其數值範圍是0 ~ 2^16-1,由於,這直接致使byte型不能自動類型提高到char,char和short直接也不會發生自動類型提高(由於負數的問題),同時,byte固然能夠直接提高到short型。

 

b. 隱式類型轉換

上面的例子中既有隱式類型轉換, 也有強制類型轉換, 那麼什麼是隱式類型轉換呢?

隱式轉換也叫做自動類型轉換, 由系統自動完成.

從存儲範圍小的類型到存儲範圍大的類型.

byte ->short(char)->int->long->float->double

 

c. 顯示類型轉換

顯示類型轉換也叫做強制類型轉換, 是從存儲範圍大的類型到存儲範圍小的類型.

當咱們須要將數值範圍較大的數值類型賦給數值範圍較小的數值類型變量時,因爲此時可能會丟失精度(1講到的從int到k型的隱式轉換除外),所以,須要人爲進行轉換。咱們稱之爲強制類型轉換。

double→float→long→int→short(char)→byte

byte a =3;編譯正確在1中已經進行了解釋。接下來將一個值爲3的int型變量b賦值給byte型變量c,發生編譯錯誤。這兩種寫法之間有什麼區別呢?

區別在於前者3是直接量,編譯期間能夠直接進行斷定,後者b爲一變量,須要到運行期間才能肯定,也就是說,編譯期間爲以防萬一,固然不可能編譯經過了。此時,須要進行強制類型轉換。

強制類型轉換所帶來的結果是可能會丟失精度,若是此數值尚在範圍較小的類型數值範圍內,對於整型變量精度不變,但若是超出範圍較小的類型數值範圍內,極可能出現一些意外狀況。

上面的例子中輸出值是 -23. 

爲何結果是-23?須要從最根本的二進制存儲考慮。

233的二進制表示爲:24位0 + 11101001,byte型只有8位,因而從高位開始捨棄,截斷後剩下:11101001,因爲二進制最高位1表示負數,0表示正數,其相應的負數爲-23。

 

d. 進行數學運算時的數據類型自動提高與可能須要的強制類型轉換

當進行數學運算時,數據類型會自動發生提高到運算符左右之較大者,以此類推。當將最後的運算結果賦值給指定的數值類型時,可能須要進行強制類型轉換。例如:

a+b會自動提高爲int, 所以在給c賦值的時候要強制轉換成byte.

2.類型轉換中的符號擴展Sign Extension

有沒有想過這麼一個問題, 當把一個byte的負數轉換爲int時, 它的值是正數仍是負數呢? 當把一個int強制轉爲爲byte, 咱們可否肯定轉換後數字的符號呢? 要理解這兩點, 咱們首先要明白計算機中數的表示, 和java中類型轉換時進行的操做.

a. 計算機中數的表示

計算機中的數都是以補碼的形式存儲的, 最高位是符號位. 正數的補碼是它自己, 而負數的補碼是原碼按位取反後加1. 這樣咱們就很清楚java中這些數據類型的範圍是怎麼獲得的.

例如: byte的範圍是-128 ~ 127. 爲何會有-128呢? 其實-128的二進制表示是 10000000, 這個補碼形式是否是很奇怪呢? 咱們找不到一個數能夠對應這樣的補碼, 其實這是-0的原碼, 那-0的補碼呢? 按位取反加1試試看, 是否是又變爲00000000呢? 因此這個多出來的-0就用來表示-128了.

有了上面的表示, 咱們就要問: 如何在類型擴展的時候保持數字的符號和值不變呢?

 

b. java中的符號擴展

1) 什麼是符號擴展

符號擴展(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),符號擴展先後數值的大小和符號都保持不變。

2) java中數值類型轉換的規則

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

因此java在進行類型擴展時候會根據原始數據類型, 來執行符號擴展仍是零擴展. 數值類型轉數值類型的符號擴展不會改變值的符號和大小.

 

c. 解析「多重轉型」問題

一個連續三次類型轉換的表達式以下:

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。

 

d. 幾個轉型的例子

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

  在將一個char型數值c轉型爲一個寬度更寬的類型時,而且不但願有符號擴展,能夠以下編碼:

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

若是須要符號擴展,則能夠以下編碼:

  首先將c轉換成short類型,它和char是 等寬度的,而且是有符號類型,再將short類型轉換成int類型時,會自動進行符號擴展,即若是short爲負數,則在左邊補上16個1,不然補上16個0.

  若是在將一個byte數值b轉型爲一個char時,而且不但願有符號擴展,那麼必須使用一個位掩碼來限制它:

  (b & 0xff)的結果是32位的int類型,前24被強制置0,後8位保持不變,而後轉換成char型時,直接截取後16位。這樣無論b是正數仍是負數,轉換成char時,都至關因而在左邊補上8個0,即進行零擴展而不是符號擴展。

  若是須要符號擴展,則編碼以下:

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

 

e.總結

實際上在數值類型轉換時,只有當遇到負數時纔會出現問題,根本緣由就是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解惑》

相關文章
相關標籤/搜索