型態轉換


型態與變數看似簡單,但每種程式語言可能都有其不同的細節,接下來要介紹的型態轉換觀念,看似只是認證題目中很常考,然而實務上,確實也有些情況,因為忽略了型態轉換而誤踏地雷的例子,因此你還是要有所瞭解。


首先,如果你寫了這個程式片段:

double PI = 3.14;

這個片段編譯時沒有問題,但如果你寫了個程式片段:

float PI = 3.14;

就會得到了possible loss of precision的編譯錯誤?這是因為在程式中寫下一個浮點數時,編譯器預設會使用double型態。就上圖而言,你想要將double長度的資料指定給float型態變數,編譯器就會很貼心地告訴你double型態放到float變數,會因為8個位元組要放到4個位元組而遺失4個位元組的資料。

你有兩種方式可以避免這個錯誤,第一個方式是在3.14後加上F,這會告訴編譯器,請用float來儲存3.14這個值。例如:

float PI = 3.14F;

另一個方式是,明確告訴編譯器,你就是要將double型態的3.14丟(Cast)float變數中,請編譯器住嘴

float PI = (float) 3.14;

編譯器看到double型態的3.14要指定給float變數,本來囉嗦地告訴你會遺失精度,但你使用(float)語法告訴編譯器,你就是要將double型態的3.14指定給float變數,別再囉嗦了,於是編譯器就住嘴不講話了,於是編譯通過,既然你不要編譯器囉嗦了,那執行時期出錯,那後果請自負,也就是如果真的因為遺失精度而發生程式錯誤了,那絕不是編譯器的問題。

再來看整數的部份。如果你寫下:

int number = 10;

這沒有問題。如果你寫下:

int number = 2147483648;

編譯時會得到integer number too large的錯誤?也許你以為原因是int變數number裝不下2147483648,因為int型態最大值是2147483647,認為這樣可以解決問題:

long number = 2147483648;

編譯時還是會得到integer number too large的錯誤,事實上,並非是number裝不下2147483648(如果是的話,編譯錯誤訊息應該是possible loss of precision),而是程式中寫下一個整數時,預設是使用不超過int型態長度。2147483648超出了int型態的長度,你要直接告訴編譯器,用long來配置整數的長度,也就是在數字後加上個L:

long number = 2147483648L;

如上就可以通過編譯了,方才談到,程式中寫下一個整數時,預設是使用不超過int型態的長度,所以下面的程式可以通過編譯:

byte number = 10;

因為10是在byte可儲存的範圍中,不過這樣不行:

byte number = 128;

128超過byte可儲存的範圍,於是會使用int儲存128,你要將int型態儲存至byte變數,就會出現possible loss of precision的編譯錯誤。
再來看運算,如果運算式中包括不同型態數值,則運算時以長度最長的型態為主,其它數值自動提昇(Promote)型態。例如:

int a = 10;
double b = a * 3.14;

在這個程式片段中,aint型態,而寫下的3.14預設是double,所以a的值被提至double空間進行運算。

如果運算元都是不大於int的整數,則自動全部提昇為int型態進行運算。下面這個片段通不過編譯:

short a = 1;
short b = 2;
short c = a + b; // possible loss of precision

雖然ab都是short型態,但Java在運算整數時,如果全部的運算元都是不大於int,那麼一律在int的空間中運算,int的運算結果要放到short,編譯器就又會囉嗦遺失精度的問題,所以你要告訴編譯器,就是要將int的運算結果丟到short,請它住嘴:

short a = 1;
short b = 2;
short c = (short) (a + b);

類似地,以下的程式片段通不過編譯:

short a = 1;
long b = 2;
int c = a + b;  // possible loss of precision

記得之前說過嗎?如果運算式中包括不同型態,則運算時會以最長的型態為主,以上面的程式而言,blong型態,於是a也被提至long空間中作運算,long的運算結果要放到int變數c,自然就會被編譯器囉嗦精度遺失了。如果這真的是你想要的,那就叫編譯器住嘴吧!

short a = 1;
long b = 2;
int c = (int) (a + b);

那麼以下你覺得會顯示多少?

System.out.println(10 / 3);

答案是3,而不是3.333333....,因為10與3會在int長度的空間中作運算,因此不會作浮點數表示,如果想得到3.333333...的結果,那麼必須有一個運算元是浮點數。例如:

System.out.println(10.0 / 3);

很無聊對吧!好像只是在玩弄語法似地!那麼,稍微看看底下的程式片段有沒有問題?

int count = 0;
while(someCondition) {

    if(count + 1 > Integer.MAX_VALUE) {
        count = 0;
    }
    else {
        count++;
    }
    ...
}

這個程式片段想作的是,在某些情況下,不斷遞增count的值,如果count超過上限就歸零,在這邊以int型態的最大值為上限。程式邏輯看似沒錯,但count + 1 > Integer.MAX_VALUE永遠不會是true,如果count已經到了2147483647,也就是int的最大值,此時記憶體中的位元組會是:

01111111 11111111 11111111 11111111

count + 1則會變為:

10000000 00000000 00000000 00000000

位元組第一個位元是1,在Java中表示一個負數,上例也就是表示-2147483648,簡單來講,最後count + 1會因為超出了int可儲存範圍而溢值,count + 1 > Integer.MAX_VALUE永遠不會成立。

Promotion 與 Cast 中還有兩個例子,你可以想想看問題是什麼?