iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0
Software Development

掌握Java神器,駕馭SpringBoot猛獸系列 第 7

第七日 從ByteCode看型別轉換

  • 分享至 

  • xImage
  •  

昨天提到Java會在編譯時實現自動裝箱和拆箱,將基礎類型和包裝類別進行轉換,同時將ByteCode進行反編譯,解讀程式碼實際運行的過程,證實使用包裝類別內建方法實現類型轉換,今日來使用JDK提供的編譯和反編譯工具,證實前幾篇提到很多次的觀念,有些「陳述式是特別設計」的使用方式,編譯後會照預設規則,以另一種方式執行

編譯與反編譯基本使用方式

Java JDK提供了 javac(編譯)用來將.java檔案編譯成 bytecode,要了解實際內容可以使用javap(反編譯),解析bytecode轉成文字內容,裡面呈現了JVM可以呈現的指令及內容,現在來段簡單的程式碼實作

// 自訂一個 Class 在main方法輸入下列執行內容
public static void main(String[] args) {
    int intNum = 99;
    float floatNum = intNum;
    System.out.println(floatNum); // output: 99.0
}

這段程式碼定義整數變數 intNum,接著將整數值賦值給浮點數 floatNum,打印 floatNum顯示 99.0,這有個問題Java是強型別,整數可以跟浮點數的類型轉換,後面會透過編譯工具來揭曉答案,先按照下面步驟產生內容

第一步將.java檔案進行編譯,打開 CMD 進入當前 .java檔案目錄,輸入 javac xxx.java執行完成,可以發現目錄下出現一個 .class檔案裡面就是編譯後的ByteCode

第二步透過反編譯查看編譯後的內容,javap -v (ByteCode檔案名稱) >> 要檢視的檔案名稱,執行成功可以看到新增了「要檢視的檔案名稱」,點進去就會看到「當前類別載入時」如何進行記憶體操作,以及main方法的運行流程

基礎類型向下兼容

基礎類型除了能與封裝類型實現自動轉換,在「特定規則下」能實現基礎類型間的資料轉換,轉換有兩個大原則

  • 數值類型可以互相轉換,但浮點數轉整數可能會影響精度
  • 整數(int)與字元(char)可以實現互相轉換

看說明不太好理解,這邊先打開上個段落反編譯後的檔案,來看數值間的轉換方式

bipush        99
istore_1
iload_1
i2f
fstore_2
getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
fload_2
invokevirtual #13                 // Method java/io/PrintStream.println:(F)V
return

其中實現流程可以發現,先建立整數常數 99,在存入記憶體#1中,在執行名為i2f的操作,後來去查資料發現是JVM將int轉成float的指令,因此後面的動作可以理解成,將整數轉化後的浮點數數值存入記憶體#2中,在實現將變數floatNum賦值操作,由結果可以知道基礎類型的資料轉型,在編譯時會加入型別轉換的指令

接著實作另一種情況,將佔用空間較大的類型,賦值給佔用空間較小的類型,基礎類型 float賦值給浮點數

float f2 = (float) 10.5;
int i2 = f2;

執行時會拋出這個錯誤,error: incompatible types: possible lossy conversion from float to int
要解決這個問題,可以透過強制轉型方式將float資料轉成整數,將程式碼改成下列做法

float f2 = (float) 10.5;
int i2 = (int) f2;
System.out.println(i2); // 10

// 重新反編譯內容
ldc           #19                 // float 10.5f
fstore_3
fload_3
f2i
istore        4
getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
iload         4
invokevirtual #20                 // Method java/io/PrintStream.println:(I)V

從編譯結果可以知道,原本的i2f變成了f2i,用法如字面上的意思一樣,是將float轉成int,相較一開始將佔用空間小的型別,轉至佔用空間大的型別,會出現錯誤警示,其原因是每個型別佔用與最大值最小值,可能會因為要向下轉型的實際值,超出變數參考類型的範圍,導致出現預期外錯誤,下面來段範例說明,可能造成此問題的情況

long f3 = Long.MIN_VALUE;
int i3 = (int) f3;
System.out.printf( "Long Min Value:%d \nActual Value:%d\n", Long.MIN_VALUE, i3);
// output:
    Long Min Value:-9223372036854775808
    Actual Value:0

從結果仔細推導內容,Long.MIN_VALUE超出了int的最小值-2147483648,不在int類型可接受長度內,因此在轉型時會將整數i3的值,強制套用0,出現資料失控的問題,造成不可控的錯誤,Java為了防範這種問題,要求以強制轉型方式進行型別轉換

最後可以得到一種結論,即使程式在運編譯時有時候會幫忙自動轉型,實際撰寫邏輯時還是要養成檢查資料的習慣,才能預防出現即使程式編譯通過,實際執行卻出現失控的資料,導致很難抓的BUG出現

參考文獻

  1. https://www.baeldung.com/java-primitives
  2. https://www.jyt0532.com/2020/02/29/jvm-byte-instruction/
  3. https://www.baeldung.com/java-lossy-conversion

上一篇
第六日 看裝拆與拆箱
下一篇
第八日 看Java編譯機制
系列文
掌握Java神器,駕馭SpringBoot猛獸30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
yale918
iT邦新手 5 級 ‧ 2023-09-21 17:30:22

蛙呀蛙呀蛙!

我要留言

立即登入留言