深入理解 Java 基本資料型別

靜默虛空發表於2019-03-10

:notebook: 本文已歸檔到:「blog

資料型別分類

Java 中的資料型別有兩類:

  • 值型別(又叫內建資料型別,基本資料型別)
  • 引用型別(除值型別以外,都是引用型別,包括 String、陣列)

值型別

Java 語言提供了 8 種基本型別,大致分為 4

  • 整數型
    • byte - 8 位。
    • short - 16 位。
    • int - 32 位。
    • long - 64 位,賦值時一般在數字後加上 lL
  • 浮點型
    • float - 32 位,直接賦值時必須在數字後加上 fF
    • double - 64 位,賦值時一般在數字後加 dD
  • 字元型
    • char - 16 位,儲存 Unicode 碼,用單引號賦值。
  • 布林型
    • boolean - 只有 true 和 false 兩個取值。

值型別和引用型別的區別

  • 從概念方面來說
    • 基本型別:變數名指向具體的數值。
    • 引用型別:變數名指向存資料物件的記憶體地址。
  • 從記憶體方面來說
    • 基本型別:變數在宣告之後,Java 就會立刻分配給他記憶體空間。
    • 引用型別:它以特殊的方式(類似 C 指標)向物件實體(具體的值),這類變數宣告時不會分配記憶體,只是儲存了一個記憶體地址。
  • 從使用方面來說
    • 基本型別:使用時需要賦具體值,判斷時使用 == 號。
    • 引用型別:使用時可以賦 null,判斷時使用 equals 方法。

:point_right: 擴充套件閱讀:Java 基本資料型別和引用型別

這篇文章對於基本資料型別和引用型別的記憶體儲存講述比較生動。

資料轉換

Java 中,資料型別轉換有兩種方式:

  • 自動轉換
  • 強制轉換

自動轉換

一般情況下,定義了某資料型別的變數,就不能再隨意轉換。但是 JAVA 允許使用者對基本型別做有限度的型別轉換。

如果符合以下條件,則 JAVA 將會自動做型別轉換:

  • 由小資料轉換為大資料

    顯而易見的是,“小”資料型別的數值表示範圍小於“大”資料型別的數值表示範圍,即精度小於“大”資料型別。

    所以,如果“大”資料向“小”資料轉換,會丟失資料精度。比如:long 轉為 int,則超出 int 表示範圍的資料將會丟失,導致結果的不確定性。

    反之,“小”資料向“大”資料轉換,則不會存在資料丟失情況。由於這個原因,這種型別轉換也稱為擴大轉換

    這些型別由“小”到“大”分別為:(byte,short,char) < int < long < float < double。

    這裡我們所說的“大”與“小”,並不是指佔用位元組的多少,而是指表示值的範圍的大小。

  • 轉換前後的資料型別要相容

    由於 boolean 型別只能存放 true 或 false,這與整數或字元是不相容的,因此不可以做型別轉換。

  • 整型型別和浮點型進行計算後,結果會轉為浮點型別

示例:

long x = 30;
float y = 14.3f;
System.out.println("x/y = " + x/y);
複製程式碼

輸出:

x/y = 1.9607843
複製程式碼

可見 long 雖然精度大於 float 型別,但是結果為浮點數型別。

強制轉換

在不符合自動轉換條件時或者根據使用者的需要,可以對資料型別做強制的轉換。

強制轉換使用括號 ()

引用型別也可以使用強制轉換。

示例:

float f = 25.5f;
int x = (int)f;
System.out.println("x = " + x);
複製程式碼

裝箱和拆箱

包裝類、裝箱、拆箱

Java 中為每一種基本資料型別提供了相應的包裝類,如下:

Byte <-> byte
Short <-> short
Integer <-> int
Long <-> long
Float <-> float
Double <-> double
Character <-> char
Boolean <-> boolean
複製程式碼

引入包裝類的目的就是:提供一種機制,使得基本資料型別可以與引用型別互相轉換

基本資料型別與包裝類的轉換被稱為裝箱拆箱

  • 裝箱(boxing)是將值型別轉換為引用型別。例如:intInteger
    • 裝箱過程是通過呼叫包裝類的 valueOf 方法實現的。
  • 拆箱(unboxing)是將引用型別轉換為值型別。例如:Integerint
    • 拆箱過程是通過呼叫包裝類的 xxxValue 方法實現的。(xxx 代表對應的基本資料型別)。

自動裝箱、自動拆箱

基本資料(Primitive)型的自動裝箱(boxing)拆箱(unboxing)自 JDK 5 開始提供的功能。

自動裝箱與拆箱的機制可以讓我們在 Java 的變數賦值或者是方法呼叫等情況下使用原始型別或者物件型別更加簡單直接。 因為自動裝箱會隱式地建立物件,如果在一個迴圈體中,會建立無用的中間物件,這樣會增加 GC 壓力,拉低程式的效能。所以在寫迴圈時一定要注意程式碼,避免引入不必要的自動裝箱操作。

JDK 5 之前的形式:

Integer i1 = new Integer(10); // 非自動裝箱
複製程式碼

JDK 5 之後:

Integer i2 = 10; // 自動裝箱
複製程式碼

Java 對於自動裝箱和拆箱的設計,依賴於一種叫做享元模式的設計模式(有興趣的朋友可以去了解一下原始碼,這裡不對設計模式展開詳述)。

? 擴充套件閱讀:深入剖析 Java 中的裝箱和拆箱

結合示例,一步步闡述裝箱和拆箱原理。

裝箱、拆箱的應用和注意點

裝箱、拆箱應用場景

  • 一種最普通的場景是:呼叫一個含型別為 Object 引數的方法,該 Object 可支援任意型別(因為 Object 是所有類的父類),以便通用。當你需要將一個值型別(如 int)傳入時,需要使用 Integer 裝箱。
  • 另一種用法是:一個非泛型的容器,同樣是為了保證通用,而將元素型別定義為 Object。於是,要將值型別資料加入容器時,需要裝箱。
  • == 運算子的兩個操作,一個運算元是包裝類,另一個運算元是表示式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。

示例:

Integer i1 = 10; // 自動裝箱
Integer i2 = new Integer(10); // 非自動裝箱
Integer i3 = Integer.valueOf(10); // 非自動裝箱
int i4 = new Integer(10); // 自動拆箱
int i5 = i2.intValue(); // 非自動拆箱
System.out.println("i1 = [" + i1 + "]");
System.out.println("i2 = [" + i2 + "]");
System.out.println("i3 = [" + i3 + "]");
System.out.println("i4 = [" + i4 + "]");
System.out.println("i5 = [" + i5 + "]");
System.out.println("i1 == i2 is [" + (i1 == i2) + "]");
System.out.println("i1 == i4 is [" + (i1 == i4) + "]"); // 自動拆箱
// Output:
// i1 = [10]
// i2 = [10]
// i3 = [10]
// i4 = [10]
// i5 = [10]
// i1 == i2 is [false]
// i1 == i4 is [true]
複製程式碼

示例說明:

上面的例子,雖然簡單,但卻隱藏了自動裝箱、拆箱和非自動裝箱、拆箱的應用。從例子中可以看到,明明所有變數都初始化為數值 10 了,但為何會出現 i1 == i2 is [falsei1 == i4 is [true]

原因在於:

  • i1、i2 都是包裝類,使用 == 時,Java 將它們當做兩個物件,而非兩個 int 值來比較,所以兩個物件自然是不相等的。正確的比較操作應該使用 equals 方法。
  • i1 是包裝類,i4 是基礎資料型別,使用 == 時,Java 會將兩個 i1 這個包裝類物件自動拆箱為一個 int 值,再代入到 == 運算表示式中計算;最終,相當於兩個 int 進行比較,由於值相同,所以結果相等。

裝箱、拆箱應用注意點

  1. 裝箱操作會建立物件,頻繁的裝箱操作會造成不必要的記憶體消耗,影響效能。所以應該儘量避免裝箱。
  2. 基礎資料型別的比較操作使用 ==,包裝類的比較操作使用 equals 方法。

小結


深入理解 Java 基本資料型別

參考資料

相關文章