走進 JDK 之 談談基本型別

秉心說發表於2019-03-31

回顧一下前面的一系列文章,

走進 JDK 之 Integer

走進 JDK 之 Long

走進 JDK 之 Float

走進 JDK 之 Byte

走進 JDK 之 Boolean

除了 chardouble,基本涵蓋了 Java 的所有基本型別。今天就來總結一下基本型別的相關知識。

基本型別概述

Java 中有 8 種基本型別,如下表所示:

基本型別 大小 最大值 最小值 包裝類 虛擬機器中符號
boolean - - - Boolean Z
char 16 bits 65536 0 Character C
byte 8 bits 127 -128 Byte B
short 16 bits 215-1 - 215 Short S
int 32 bits 231-1 231 Integer I
long 64 bits 263-1 -263 Long J
float 32 bits 3.4028235e+38f -3.4028235e+38f Float F
double 64 bits 1.7976931348623157e+308 -1.7976931348623157e+308 Double D

每種基本型別所佔的儲存空間大小不會隨著機器硬體架構的變化而變化,具有良好的可移植性。

數值型別有 byteshortintlongfloatdouble。其中 byteshortintlong 是整數型別。floatdouble 是浮點數型別,分別表示單精度和雙精度,遵循 IEEE 754 浮點數標準。Java 中所有數值型別都是有符號數,最高位表示符號位。

boolean 是布林型別,只有兩個值 TRUEFALSE,在 JVM 中當做 int 處理,兩個值分別為 10

char 是字元型別,為 Unicode 編碼。在某些應用場景下,可以把 char 當作無符號數來處理。

對於上面表格中的內容,還有幾點需要說明一下:

溢位問題

整數型別都有自己的取值範圍,如果強行超出它的範圍會怎麼樣呢?如下面程式碼:

public void test() {
    byte max = Byte.MAX_VALUE;
    byte over = (byte) (max + 1);
    System.out.println(over);
}
複製程式碼

列印結果是 -128,即 Byte.MIN_VALUE。你可以把值域範圍想象成時鐘的刻度,最大值是 12 ,最小值是 0 ,已經到了最大值 12 ,你還要再加 1 ,就發生了溢位,又回到了最小值 0 。

棧上大小

在棧上,byteshortbooleanchar 佔用的空間和 int 是一致的,都是佔一個 slotlongdouble 是佔用兩個 slot。具體原因在之前的文章中具體分析過,這裡再總結一下:

  • 區域性變數表可以看成一個 slot 陣列,這樣設計方便使用索引來獲取資料
  • 操作碼是單位元組的,最多隻有 256 個位元組碼指令,不可能為每一個基本資料型別提供完整的指令支援。

當然,這僅僅只是針對棧上,對於堆上和陣列中分配的基本型別,其大小還是和表中匹配的。

  • 不同型別的位元組碼指令處理

這塊內容在 走進 JDK 之 Boolean 中詳細介紹過。操作碼是單位元組的,最多隻有 256 個位元組碼指令,不可能為每一個基本資料型別提供完整的指令支援。大部分的指令都沒有支援 bytecharshortboolean 則更慘,沒有任何指令支援 boolean 型別。對於這些不支援的指令型別,一律使用 int 的相關指令代替。

對於不確定的情況,你可以看一下 class 檔案中的位元組碼:

javac Test.java
javap -v Test.class
複製程式碼

為什麼需要基本型別 ?

基本所有語言都有基本資料型別,Java 當然也是如此。標榜 萬物皆物件 的 Java 為什麼需要基本資料型別的存在呢?回顧一下你的職業生涯中寫過的程式碼,基本資料型別出現的概率可以說相當之高,基本資料型別是直接儲存在棧記憶體的,而 new 出來的物件是儲存在堆記憶體中的。顯然物件佔用的記憶體是高於基本資料型別的。物件在記憶體中儲存的佈局可以分為 3 塊區域:物件頭例項資料對齊填充。如果把你的程式碼中所有基本型別全部替換為其包裝類,無疑會佔用更多的記憶體,也降低了執行效率。

為什麼需要包裝類 ?

就衝那句 萬物皆物件 ,當然也得有包裝類了!開個玩笑而已 ... 基本資料型別佔用記憶體更少,執行速度更快,但它也有辦不到的事情,比如我要構造一個包含 intList 集合:

List list = new ArrayList<int>();
複製程式碼

顯然這是沒法通過編譯的。這裡我們傳進去的必須是一個物件,所以基本資料型別還必須得有一個包裝類。包裝類中包裝了基本型別的數值,並且提供了一系列處理數值的方法,就像前面分析的過得原始碼中各種方法,豐富了基本資料型別的功能。

自動拆箱與自動裝箱

把基本資料型別轉換成包裝類的過程叫做裝箱。

把包裝類轉換成基本資料型別的過程叫做拆箱。

在Java SE 5.0 之前,裝箱和拆箱需要手動進行。可能 Java 開發者們覺得這樣實在太不人性化了,一切應該以提高生產力為主,在 Java SE 5.0 中就推出了自動裝箱和拆箱。以 Integer 為例:

Integer i = 10;  //自動裝箱
int b = i;     //自動拆箱
複製程式碼

我們已經解析過 Integer 的原始碼,不難想象,自動裝箱和拆箱必然是呼叫了包裝類的某些方法,javap 看一下位元組碼:

2: invokestatic  #2         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: invokevirtual #3         // Method java/lang/Integer.intValue:()I
複製程式碼

很明瞭,之前的程式碼經過編譯器編譯,已經改變了模樣:

Integer i = Integer.valueOf(10);  //裝箱
int b = i.intValue();     //拆箱
複製程式碼

還記得 valueOf() 方法嗎?

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
複製程式碼

通過 IntegerCache 快取了 -128127。在此範圍內直接返回快取的例項,否則建立新物件。

不僅僅是 Integer,其他基本資料型別也都是使用類似 valueOf()xxxValue() 方法來進行自動裝箱和拆箱的。

現在你應該知道了自動裝箱和自動拆箱實際上是編譯器在裡面做了手腳,和 Java 虛擬機器並沒有什麼關係。編譯器在生成類的位元組碼時,插入必要的方法呼叫。虛擬機器只是執行這些位元組碼。

最後

走進 JDK 系列的基本資料型別部分就說到這裡了,還有什麼疑問的話,歡迎關注我的公眾號 秉心說 給我留言。

下一篇是 走進 JDK 之 String,不出意外的話應該要過幾天,String 的內容還是比較多的,可以先閱讀閱讀我之前的一篇文章 String 為什麼不可變?

文章首發於微信公眾號: 秉心說 , 專注 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!

走進 JDK 之 談談基本型別

相關文章