Java學習之路04—類型轉換


架構圖

前言

我們再進行Java程式編寫時,常常會遇到運算的問題,例如常見的整數加減、浮點數的乘除、甚至是字元類型的運算。但很多時候運算的操作數不一定都是相同數據類型的

這時候我們就需要仰賴型別轉換(Casting)將雙方數據類型轉換成統一類型,才可以繼續運算。當我們說到型別轉換,主要就是自動型別轉換強制型別轉換兩種

布林類型數據不能進行型別轉換

自動類型轉換

自動型別轉換顧名思義就是不需要使用者進行手動添加操作,編譯器自行就會進行處理的型別轉換,所以又稱為隱式型別轉換。出現條件為轉換後變數類型的存儲範圍大於轉換前的存儲範圍時,例如:

整數之間
若是相同數據類型之間的自動型別轉換,通常不會造成數據丟失或發生錯誤

byte b = 1;
short s = 256;
int i = 65538;
long l = 458294l;

l = i; // 自動型別轉換
i = s; // 自動型別轉換
s = b; // 自動型別轉換

上述例子中,編譯器會自動將語句進行轉換,例如l = i,經過編譯器處理會變成l = (long)i
整數與浮點數
浮點數類型的存儲範圍較整數來得廣,所以整數賦值給浮點數也是自動型別轉換的一種

int i = 123456789;
float f = 12.3f;
f = i; // 自動型別轉換

不過整數轉浮點數的過程中很有可能發生賦值數據不一致問題。例如我們將上述範例執行一次,發現雖然整數可以自動轉換成浮點類型,但精準度已經丟失

字元
字元類型數據也可以與浮點和整數之間進行轉換,不過short與char之間不能進行自動型別轉換,因為根據順序轉換表,char類型是直接轉換成int

int i = 10;
char c = 'A';
float f = 12.3f;

i = c; // 自動型別轉換
f = c; // 自動型別轉換

強制類型轉換

/* 等號左側變數範圍較大 */
int a = 65;
char ch = (char)65536;
ch = (char)a;

/* 特殊計算用途 */
float height = 12.5f;
float width = 2.0f;
int area = (int)(height*width); // 注意乘法運算要使用括號,不然最後結果還是float類型

我們可以從上述例子看到,等號右側的變數存儲範圍較等號左側大時,編譯器就會發出警告,"這個操作可能會造成賦值錯誤"

由此可知只要數據範圍是由大轉小,就需要使用強制類型轉換。因此我們又稱強制類型轉換為Narrow Casting

另一種使用場景是進行運算時有特殊需求,需要將計算結果轉換成特定變數類型時,這時我們也要使用強制類型轉換,不然編譯器會覺得我們的操作很危險

float f1 = 123456789f;
int a = (int)f1; // (1)

float f2 = 12.95f 
a = (int)f2; // (2) 

不意外,第一種狀況我們在自動類型轉換中也看到過,整數與浮點數之間的運算會存在一些誤差

第二種狀況也很好理解,單存就是整數類型不能表示小數,因此造成轉換後的誤差

float f1 = 999999999999f;
float f2 = 99999999999f;

int a = (int)f1;
System.out.println("int a = "+a);


a = (int)f2;
System.out.println("int a = "+a);


a = (int)2147483648F;
System.out.println("int a = "+a);


a = (int)99999999999l;
System.out.println("int a = "+a);

既然如此我們將浮點數的變數值推至整數的極限,看看賦值後打印結果如何

可以從執行結果發現,若是相同變數族群(例如都是整數類型),進行強制轉換時發生溢位,結果會依照最高位形式呈現負數(最高位為1代表負數),且會隨者溢位長度而改變

但假如是浮點數轉整數的狀況,不管賦值的浮點數超出範圍多大,轉換後的結果均相同,整數經過轉換後,都會卡在最大值,也就是2147483647

類型轉換表

以上是變數類型之間的類型轉換順序表,類型轉換的目的是將多個不同類型的變數轉換成一個統一的變數類型,通常都是變數之間儲存範圍最長的那個

黑色實心箭頭代表類型轉換行為是編譯器自動產生的,並且不用考慮數據丟失問題

int a=65;
byte b = 127;
char ch='o';
a = b + ch; 

例如上面這個案例,b和ch都會轉換成int類型,然後才會操作進而賦值

黑色虛線箭頭則表示雖然編譯器會自動進行類型轉換,但可能會有精度丟失問題。自動轉換下通常是int轉float與long轉float, double時會發生問題

int i = 123456789;
long l = 123456789l;
float f = 0.0f;
double d = 0.0;

f = i; // 可能發生精度丟失
f = l; // 可能發生精度丟失

d = i; // 不會發生精度丟失
d = l; // 可能發生精度丟失

紅色箭頭代表需要使用強制類型轉換,而且可能造成精度丟失。垂直方向視為同數據類型之間的轉換,水平方向代表跨數據類型之間的轉換

double d = 0d;
float f = 10.0f;
int i = 20;
f += (float)i; // 強制將i轉換成float類型再進行相加
d = f+i;

上述的例子中,f += (float)i;這條語句若是沒有加上強制轉型,編譯器將會報錯

因為float類型與int類型之間進行運算時會自動轉型成double類型(大家統一的浮點格式,且不失精度),但最終賦值對象是float類型,由小轉大會發生錯誤,所以需要提前進行強制轉換

從上圖中我們可以看出變數之間運算時的轉型順序,特別要注意運算完成後進行賦值時的雙方變數範圍,以此決定是否要進行強制類型轉換

陷阱

關於後綴和強制轉換的區別

float f1 = (float)1.23456; // (1)
float f2 = 1.23456f; // (2)

上述兩種狀況是不一樣的。第一種狀況1.23456是一個double類型,經過強制轉換變成一個浮點類型,結果可能造成精度丟失;第二種狀況1.23456在宣告時就已經是一個float類型了

整數的預設類型與轉換

int number = 2147483648 // 出錯
long l = 2147483648; // 出錯
long l = 2147483648L;

由於整數常數的預設類型為int,因此不進行任何後綴修飾時,通常是表示為int類型

一個整數常數若是超過int上下限時,編譯器就會報錯,這跟等號左側的變數範圍無關,重點在於我們需要告訴編譯器,現在使用的這個常數為long類型,所以不用擔心賦值的範圍超過int上下限

便數類型小於預設類型

short s1 = 12;
short s2 = 34;
short s3 = s1 + s2; // 出錯
short s3 = (short)(s1 + s2);

若是變數類型都是不大於int的運算元,則編譯器會自動將變數類型提升至int類型進行計算,因次上述例子的運算結果需要進行強制類型轉換

操作運算轉換

short s = 12;
long l = 34;
int i = s + l; // 出錯
int i = s + (int)l;

上述例子中i為int類型,但運算結果為long類型,因此需要在short碰到long之前先進行強制類型轉換,讓結果變成int類型變數

有一則關於 Java學習之路04—類型轉換 的留言

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

%d 位部落客按了讚: