Java 學習筆記 12 – 包裝類別


Posted by vickyh1315 on 2024-05-29

Java 有基本型別和參考型別(物件),基本型別在運算的時候效率更高,但多數套件(java.util)只能處理物件,兩者之間轉換需求,會用到包裝類別。

包裝類別產生的物件會包含一個字段,而這個字段可以儲存資料型別,也就是說,基本型別可以包裝到包裝類別裡。

Java API 的 java.lang package 底下有與 8 個基本型別相對應的類別,統稱包裝類別,之所以需要包裝類別是因為:

  1. 將基本型別包裝進物件
    基本型別變數是以傳值(pass by value)方式在函數中進行資料傳遞;優點在於效率較高,但不能以方法對自己有任何操作,但轉成物件,就可通過方法去做到這件事(被當參數放進函式時或轉換基本型別的值)。
基本資料型別 Wrapper class 類別 建構子參數型別
byte Byte byte, String
short Short short, String
int Integer int, String
long Long long, String
float Float float, double, String
double Double double, String
char Character char
boolean Boolean boolean, String
  1. java.util API 只能處理物件
    Java API 的 java.util 套件,例如 Scanner、Date、Collection 等,會需要以物件傳入,才能使用方法。
  2. Collection Framework(即 java.util.Collection),像是 ArrayList 跟 Vector,只能儲存物件(參考型別)或是非基本型別
  3. 物件可以在多執行緒的情況下支持同步
    可實作 serializable 介面,後面文章會詳述。

轉換過程稱為裝箱(boxing),例如常用的 Integer.valueOf(),而裝箱後的物件即可使用本身的方法操作(也有常數可使用,例如 Integer.MAX_VALUE)。

class IntergerWrapping {

    public static void main() {
        int num = 1;
        int num2 = 1;

        Integer integerWrapper = Integer.valueOf(num);
        Integer integerWrapper2 = Integer.valueOf(num2);

        System.out.println(integerWrapper.doubleValue() / 3); // 0.3333333333333333
        System.out.println(integerWrapper.compareTo(integerWrapper2)); // 0
        System.out.println(integerWrapper.hashCode() == integerWrapper2.hashCode()); // true
        System.out.println(integerWrapper.equals(integerWrapper2)); // true
        System.out.println(integerWrapper == integerWrapper2); // true
    }

}

觀察上面的程式:

  • Integer.valueOf(num):手動呼叫 Integer 的 valueOf() 方法包裝 num 這個基本型別變數
  • doubleValue():取得該物件 double 型別的值,可以換成同類型的方法,例如這邊可用 intValue()floatValue()
  • compareTo():可與另一個同類別物件比較,相同回傳 0,小於參數的值回傳 -1,大於則 1
  • 轉換成類別後仍然存在相同記憶體位址

Autoboxing 自動裝箱

Java 1.5 前的版本將參考型別變數與基本型別變數視為完全不同的存在(完全不能轉換),以 i++ 這個運算式,在當時編譯會回報錯誤。

Integer i = new Integer(55);
i++; // 編譯器認為 i 是包裝類別,不具基本型別運算功能

實際執行時,需先以 intValue() 取得 i 值,進行 +1 運算後,再重新包裝類別。

i = Integer.valueOf(i,intValue() + 1);
// 雖然 new Integer() 當時可行,但官方不建議,Java 9 開始 Wrapper Class 的建構子都標示 Deprecated

針對無法轉換的不便,逐漸新增功能,至 Java 5 開始提供自動裝箱/拆箱的特性,編譯器會參考宣告數的型別,自動判斷能否裝箱,就是自動裝箱機制,相當於語法糖的概念,宣告包裝類別或當成參數放入時就會用到。

Integer num1 = new Integer.valueOf(55); // Manual
Integer num2 = 55; // Autoboxing
System.out.println(num1 == num2);    // true

Autoboxing 應用在許多場景,紀錄幾個常見的:

Autoboxing 的應用 - 1
ArrayList 類別的 add 方法,以值傳入編譯器會自動轉換成 Integer 類別,再放入 ArrayList,也是運用 Autoboxing。

ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Integer max = Collections.max(list);

Autoboxing 的應用 - 2
Scanner 搭配邏輯判斷也會運用 Autoboxing。

Scanner sc = new Scanner(System.in);
Integer i = sc.nextInt();    // Autoboxing
int i = sc.nextInt();        // Autoboxing + Autounboxing
System.out.println(i);

Unboxing 自動拆箱

自動裝箱的相反即為自動拆箱,就是自動取出包裝類別的基本型別資料,變數(integerWrapper)會參考自身類別。

int num = 1;
Integer integerWrapper = Integer.valueOf(num); // 裝箱
Integer integerWrapper2 = Integer.valueOf(num); // 自動裝箱
int numAgain = integerWrapper2; // 自動拆箱

進行運算時會自動裝箱及拆箱

Integer num2 = 10;
System.out.println(number++); // 自動拆箱

Boolean value = true;
System.out.println( value && true); // 自動拆箱

編譯器的自動裝箱、拆箱功能細節

Integer i = null;
int j = i;

這段程式碼編譯成功,但執行會失敗,中間編譯器會展開這段程式,因為 intValue() 沒有參考物件(localObject 為 null,null 代表一個特殊物件,任何類別宣告變數都可以指向 null,表示沒有參考至物件實體)。

Object localObject = null;
int i = localObject.intValue();

Overload(重載)的執行判斷

前面文章提到 Overload 的概念,是在同名、同引數個數下,編譯器會將這些函式視為不同,方便開發人員對函式的命名,對於 Wrapper Class 的判斷,會走過這些流程:

  1. 搜尋同名、同引數函式
  2. Promotion 型態晉升/Autoboxing
  3. 作為參數放進函式

情況一

// 1
public void methodA(int x){};

// 2
public void methodA(long x){};

// 3
public void methodA(Integer x){};

// 4
public void methodA(Long x){};

// 5
public void methodA(int... x){};

函數呼叫 methodA(10),編譯器中間做了:

  1. 找出上面的 1-5 方法,判斷 1 最符合(同為 int,只有一個引數)
  2. 引數型別一樣是 int,不需要 Promotion 與 Autoboxing
  3. 放入參數 + 執行 1 方法

情況二

// 2
public void methodA(long x){};

// 3
public void methodA(Integer x){};

// 4
public void methodA(Long x){};

// 5
public void methodA(int... x){};

相同地呼叫 methodA(10),此情況流程:

  1. 找出方法 2-5,判斷 2 最符合(一樣基本型別)
  2. 引數型別 long,執行 Promotion
  3. 放入參數 + 執行 2 方法

情況三

// 3
public void methodA(Integer x){};

// 4
public void methodA(Long x){};

// 5
public void methodA(int... x){};

相同地呼叫 methodA(10),此情況流程:

  1. 找出方法 3-5,判斷 3 最符合(Integer 為 int 包裝類別)
  2. 引數型別 Integer,執行 Autoboxing
  3. 放入參數 + 執行 3 方法

情況四

// 4
public void methodA(Long x){};

// 5
public void methodA(int... x){};

相同地呼叫 methodA(10),此情況流程:

  1. 找出方法 4、5,判斷 5 最符合(型別為 int,引數可放入一個就好)
  2. 放入參數 + 執行 5 方法

情況五

// 4
public void methodA(Long x){};

只剩方法 4 的情況下,編譯器一樣會呼叫,但執行會報錯:

  1. 找出方法 4
  2. 執行 Autoboxing 轉為 Integer,但 Integer 物件不會自動 Promotion 為 Long(雖然此方法行不通,但編譯器也不會先進行 Promotion 為 long,再 Autoboxing 為 Long)
  3. 回報 incompatible types: int cannot be converted to Long 的錯誤

常用的包裝類別方法

  • toString():每個包裝類別的預設寫法不同
  • valueOf()
  • compareTo():可與另個同類別物件比較,相同回傳 0,小於參數的值回傳 -1,大於則 1
  • parse[Type]():例如 parseIntparseShort,可轉換至指定型別
  • equals():相對於 == 比較記憶體位址 & 值,equals() 只比較值

包裝類別方便程式撰寫的使用,但運算時會影響執行效率,但不能完全取代基本資料型別。


#java







Related Posts

[第九週] 從 PHP 連線到 MySQL 資料庫

[第九週] 從 PHP 連線到 MySQL 資料庫

物件導向

物件導向

Leetcode JS 2676. Throttle

Leetcode JS 2676. Throttle


Comments