基本型態包裹器
May 24, 2022使用基本型態目的在於效率,然而更多時候,會使用類別建立實例,因為物件本身可以攜帶更多資訊,如果要讓基本型態像物件一樣操作,可以使用 Long
、Integer
、Double
、Float
、Boolean
、Byte
等類別來包裹(Wrap)基本型態。
包裹器
Long
、Integer
、Double
、Float
、Boolean
、Byte
類別主要目的,就是提供物件實例作為「殼」,將基本型態包裹在物件之中,如此就可以操作這個物件,就像是將基本型態當作物件操作。來看個簡單的例子:
package cc.openhome;
public class IntegerDemo {
public static void main(String[] args) {
int data1 = 10;
int data2 = 20;
var wrapper1 = Integer.valueOf(data1);
var wrapper2 = Integer.valueOf(data2);
System.out.println(data1 / 3);
System.out.println(wrapper1.doubleValue() / 3);
System.out.println(wrapper1.compareTo(wrapper2));
}
}
基本型態包裹器都是歸類於 java.lang
套件中,如果要使用 Integer
包裹 int
型態資料,方法之一是透過 Integer.valueOf
。
不要使用 new
建立基本型態包裹器,從 Java SE 9 開始,基本型態包裹器的建構式都標示為棄用(Deprecated)。
若運算式中都是 int
,就只會在 int
空間中做運算,結果會是 int
整數,因此 data1 / 3
就會顯示 3
的結果。可以操作 Integer
的 doubleValue
將包裹值以 double
型態傳回,如此就會在 double
空間中做相除,結果就會顯示 3.33333333333…。
Integer
提供 compareTo
方法,可與另一個 Integer
物件進行比較,如果包裹值相同就傳回 0,小於 compareTo
傳入物件包裹值就傳回 -1,否則就是 1,與 ==
或 !=
只能比較是否相等或不相等,compareTo
方法傳回更多資訊。
自動裝箱
除了使用 Integer.valueOf
,也可以直接透過自動裝箱(Auto boxing)包裹基本型態:
Integer number = 10;
編譯器會自動判斷是否能進行自動裝箱,在上例中你的 number
會參考 Integer
實例;同樣的動作可適用於 boolean
、byte
、short
、char
、long
、float
、double
等基本型態,分別會使用對應的 Boolean
、Byte
、Short
、Character
、Integer
、Long
、Float
或 Double
包裹基本型態。
方才的範例可以寫為:
Integer data1 = 10;
Integer data2 = 20;
System.out.println(data1.doubleValue() / 3);
System.out.println(data1.compareTo(data2));
程式看來簡潔許多,data1
與 data2
在運行時會參考 Integer
實例,可以直接進行物件操作。自動裝箱運用的方法還可以如下:
int number = 10;
Integer wrapper = number;
自動拆箱
可以自動裝箱,也可以自動拆箱(Auto unboxing),也就是自動取出包裹器中的基本形態資訊。例如:
Integer wrapper = 10; // 自動裝箱
int foo = wrapper; // 自動拆箱
wrapper
會參考至 Integer
,若被指定給 int
型的變數 foo
,則會自動取得包裹的 int
型態再指定給 foo
。
在運算時,也可以進行自動裝箱與拆箱,例如:
Integer number = 10;
System.out.println(number + 10);
System.out.println(number++);
上例中會顯示 20 與 10,編譯器會自動自動裝箱與拆箱,也就是 10 會先裝箱,然後在 number + 10
時會先對 number
拆箱,再進行加法運算;number++
該行也是先對i拆箱再進行遞增運算。再來看一個例子:
Boolean foo = true;
System.out.println(foo && false);
同樣地,foo
會參考至 Boolean
實例,在進行 &&
運算時,會先將 foo
拆箱,再與 false
進行 &&
運算,結果會顯示 false
。
裝箱、拆箱的內幕
裝箱與拆箱的功能事實上是編譯器蜜糖(Compiler sugar),也就是編譯器讓你撰寫程式時吃點甜頭,編譯時期依撰寫的語法,決定是否進行裝箱或拆箱動作。例如:
Integer i = 100;
編譯器會自動將程式碼展開為:
Integer i = Integer.valueOf(100);
下面的程式是可以通過編譯的:
Integer i = null;
int j = i;
但是在執行時期會有錯誤,編譯器會將之展開為:
Integer integer = null;
int i = integer.intValue();
在 Java 程式碼中,null
代表一個特殊物件,任何類別宣告的參考名稱都可以參考至 null
,表示該名稱沒有參考至任何物件實體,這相當於有個名牌沒有任何人佩戴。在上例中,由於 i
並沒有參考至任何物件,就不可能操作 intValue
方法,就相當於有個名牌沒有人佩戴,你卻要求戴名牌的人舉手,這是一種錯誤,會出現 NullPointerException
的錯誤訊息。
編譯器蜜糖通常提供了方便性,但也因此隱藏了一些細節,別只顧著吃糖而忽略了該知道的觀念。來看看,如果你如下撰寫,結果會是如何?
Integer i1 = 100;
Integer i2 = 100;
if (i1 == i2) {
System.out.println("i1 == i2");
}
else {
System.out.println("i1 != i2");
}
如果只看 Integer i1 = 100
,就好像在看 int i1 = 100
,直接使用 ==
進行比較,有的人會理所當然回答顯示 i1 == i2,那麼底下這個呢?
Integer i1 = 200;
Integer i2 = 200;
if (i1 == i2) {
System.out.println("i1 == i2");
}
else {
System.out.println("i1 != i2");
}
程式碼只不過將 100 改為 200,但執行結果會顯示 i1 != i2,這是為何?先前提過,裝箱是編譯器蜜糖,以上例來說,實際上會使用 Integer.valueOf
來建立 Integer
實例,查看 Integer.java 的 valueOf
的實作內容:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
這段程式碼簡單來說,就是如果傳入的 int
在 IntegerCache.low
與 IntegerCache.high
之間,那就嘗試看看先前快取(Cache)中有沒有包裹過相同的值,如果有就直接傳回,否則就使用 new
建構新的 Integer
實例。
IntegerCache.low
預設值是 -128,執行時期無法更改,IntegerCache.high
預設值是 127,可以於啟動 JVM時,使用系統屬性 java.lang.Integer.IntegerCache.high
來指定。例如:
> java -Djava.lang.Integer.IntegerCache.high=300 cc.openhome.Demo
如上指定之後,Integer
就會針對 -128 到 300 範圍中建立的包裹器進行快取,而針對先前 i1
與 i2
包裹 200 時,使用 ==
比較的結果,就又顯示 i1 == i2 了。
別使用 ==
或 !=
來比較兩個物件實質內容值是否相同(因為 ==
與 !=
是比較物件參考),而要使用 equals
。例如以下的程式碼:
Integer i1 = 200;
Integer i2 = 200;
if (i1.equals(i2)) {
System.out.println("i1 == i2");
}
else {
System.out.println("i1 != i2");
}
無論實際上 i1
與 i2
包裹的值座落在哪個範圍,只要 i1
與 i2
包裹的值相同,equals
比較的結果就會是 true
。