【Java 溫故而知新系列】基礎(chǔ)知識(shí)-03 基本類型對(duì)應(yīng)之包裝類
1、包裝類都有哪些?
基本類型都有對(duì)應(yīng)的包裝類型,這些包裝類提供了一種面向?qū)ο蟮姆绞絹?lái)處理基本數(shù)據(jù)類型,允許它們被用于需要對(duì)象的場(chǎng)景,如集合框架、泛型等。
對(duì)應(yīng)關(guān)系:
| 基本類型 | 包裝類型 |
|---|---|
| boolean | Boolean |
| byte | Byte |
| char | Character |
| float | Float |
| int | Integer |
| long | Long |
| short | Short |
| double | Double |
2、包裝類特點(diǎn)
- 封裝性:所有的包裝類都是 final 類,這意味著它們不能被繼承。這種設(shè)計(jì)確保了包裝類的行為和特性的一致性,從而避免了子類可能引入的不確定性。
- 不可變性:包裝類的實(shí)例一旦被創(chuàng)建后,其中保存的基本數(shù)據(jù)類型數(shù)據(jù)就不能再被改變。這種不可變性使得包裝類在多線程環(huán)境中更加安全,避免了因數(shù)據(jù)被意外修改而導(dǎo)致的錯(cuò)誤。
- 提供方法:包裝類封裝了許多實(shí)用的方法,提供了豐富的功能。例如,它們支持?jǐn)?shù)據(jù)類型轉(zhuǎn)換、判斷字符串的大小寫(xiě)、以及獲取最大值和最小值等。
- 繼承關(guān)系:除了 Character 和 Boolean 之外,其他所有的包裝類都繼承自 Number 類。這種繼承關(guān)系使得這些包裝類能夠共享一些通用的功能和特性,例如數(shù)字的比較和轉(zhuǎn)換,這為在不同數(shù)值類型之間的操作提供了一致的接口。
代碼為證(繼承Number類并實(shí)現(xiàn)intValue方法的類):

3、為什么會(huì)出現(xiàn)包裝類?
既然有基本類型了,為什么還會(huì)出現(xiàn)對(duì)應(yīng)的包裝類?
我覺(jué)得根本原因還是因?yàn)镴ava是面向?qū)ο蟮恼Z(yǔ)言,基本數(shù)據(jù)類型不能參與面向?qū)ο缶幊蹋?/p>
對(duì)象操作:在Java中,許多集合類和框架方法需要對(duì)象作為參數(shù),而不是基本數(shù)據(jù)類型。為了滿足這一需求,包裝類提供了將基本數(shù)據(jù)類型轉(zhuǎn)換為對(duì)象的機(jī)制。通過(guò)使用包裝類,我們可以輕松地在這些方法中傳遞基本數(shù)據(jù)類型。
Null值處理:基本數(shù)據(jù)類型無(wú)法為null,而包裝類則可以。這一特性在某些情況下非常有用,例如在方法參數(shù)中,需要表示可選值或缺省值時(shí)。通過(guò)使用包裝類,我們能夠更靈活地處理這些場(chǎng)景,確保代碼的健壯性和可讀性。這種設(shè)計(jì)使得我們?cè)谔幚頂?shù)據(jù)時(shí),可以更方便地進(jìn)行null值檢查,并在需要時(shí)安全地進(jìn)行區(qū)分,從而提高了代碼的靈活性。
4、裝箱與拆箱
裝箱(Boxing):是將基本數(shù)據(jù)類型轉(zhuǎn)換為相應(yīng)的包裝類的過(guò)程。
拆箱(Unboxing):是將包裝類轉(zhuǎn)換為基本數(shù)據(jù)類型的過(guò)程。
手動(dòng)裝箱、拆箱
手動(dòng)裝箱:使用一個(gè)本地類型的值創(chuàng)建一個(gè)對(duì)應(yīng)包裝類對(duì)象的過(guò)程
1 int num = 10; 2 3 Integer int1 = new Integer(num); // 手動(dòng)裝箱方式一 4 5 Integer int2 = Integer.valueOf(num); // 手動(dòng)裝箱方式二
手動(dòng)拆箱:使用 Integer 類型對(duì)象的 intValue() 方法來(lái)獲取這個(gè)對(duì)象的 int 值
1 Integer number= new Integer(23); 2 int num = number.intValue(); // 手動(dòng)拆箱
自動(dòng)裝箱、拆箱
Java 5引入了自動(dòng)裝箱(Auto-boxing)和自動(dòng)拆箱(Auto-unboxing)機(jī)制,簡(jiǎn)化了基本數(shù)據(jù)類型與包裝類之間的轉(zhuǎn)換過(guò)程。
自動(dòng)裝箱是將基本數(shù)據(jù)類型自動(dòng)轉(zhuǎn)換為其對(duì)應(yīng)的包裝類對(duì)象的過(guò)程。自動(dòng)裝箱的底層原理其實(shí)就是通過(guò)調(diào)用包裝類的valueOf()方法來(lái)實(shí)現(xiàn)的;同理自動(dòng)拆箱就是通過(guò)調(diào)用包裝類的xxxValue()方法來(lái)實(shí)現(xiàn)的。
以Integer 與 int 舉例:
Integer x = 2; // 裝箱 調(diào)用了 Integer.valueOf(2) int y = x; // 拆箱 調(diào)用了 x.intValue()
5、包裝類的緩存池
簡(jiǎn)要介紹
Java中的包裝類緩存機(jī)制是為了優(yōu)化性能和節(jié)省內(nèi)存而設(shè)計(jì)的。
它為整型(Byte、Short、Integer、Long)、字符型(Character)和布爾型(Boolean)的包裝類提供了緩存,確保在這些類型的小范圍值之間可以復(fù)用對(duì)象。而對(duì)于浮點(diǎn)數(shù)類型的包裝類(Float、Double),則沒(méi)有這種緩存機(jī)制,意味著每次都需要?jiǎng)?chuàng)建新的對(duì)象。
這樣一來(lái),Java在處理常用值時(shí)更加高效,但在浮點(diǎn)數(shù)處理上則相對(duì)簡(jiǎn)單直接。
緩存范圍
對(duì)于 Integer 類,Java會(huì)緩存范圍在 -128 到 127 之間的所有整數(shù)。
對(duì)于 Byte、Short 和 Character 類,緩存的范圍也是類似的。具體范圍如下:
Byte:-128 到 127
Short:-128 到 127
Character:0 到 127(即所有的ASCII字符)
Boolean:只有 true 和 false 兩個(gè)值會(huì)被緩存。
如何觸發(fā)緩存
只有調(diào)用 valueOf() 方法時(shí),如果要?jiǎng)?chuàng)建的值已經(jīng)被緩存,則會(huì)觸發(fā)緩存機(jī)制。如果要?jiǎng)?chuàng)建的 Integer 對(duì)象的值在預(yù)定范圍內(nèi),則返回緩存的對(duì)象,如果不在范圍內(nèi),則直接新創(chuàng)建一個(gè)對(duì)象。
我們來(lái)查看 Integer 類的 valueOf() 方法的源代碼(valueOf() 方法就是先判斷值是否在緩存池中,如果在的話就直接返回緩存池的內(nèi)容,不存在就創(chuàng)建一個(gè)新對(duì)象,跟上面我們所說(shuō)的邏輯是一致):
1 /**
2 * Returns an {@code Integer} instance representing the specified
3 * {@code int} value. If a new {@code Integer} instance is not
4 * required, this method should generally be used in preference to
5 * the constructor {@link #Integer(int)}, as this method is likely
6 * to yield significantly better space and time performance by
7 * caching frequently requested values.
8 *
9 * This method will always cache values in the range -128 to 127,
10 * inclusive, and may cache other values outside of this range.
11 *
12 * @param i an {@code int} value.
13 * @return an {@code Integer} instance representing {@code i}.
14 * @since 1.5
15 */
16 public static Integer valueOf(int i) {
17 if (i >= IntegerCache.low && i <= IntegerCache.high)
18 return IntegerCache.cache[i + (-IntegerCache.low)];
19 return new Integer(i);
20 }
編譯器會(huì)在自動(dòng)裝箱過(guò)程調(diào)用 valueOf() 方法,因此多個(gè)值相同且值在緩存池范圍內(nèi)的 Integer 實(shí)例使用自動(dòng)裝箱來(lái)創(chuàng)建,那么就會(huì)引用相同的對(duì)象。
1 Integer m = 123;
2 Integer n = 123;
3 System.out.println(m == n); // true
在 Java 8 中,Integer 緩存池的大小默認(rèn)為 -128~127。
在 jdk 1.8 所有的數(shù)值類緩沖池中,Integer 的緩沖池 IntegerCache 很特殊,這個(gè)緩沖池的下界是 - 128,上界默認(rèn)是 127,但是這個(gè)上界是可調(diào)的,在啟動(dòng) jvm 的時(shí)候,通過(guò) -XX:AutoBoxCacheMax=<size> 來(lái)指定這個(gè)緩沖池的大小,該選項(xiàng)在 JVM 初始化的時(shí)候會(huì)設(shè)定一個(gè)名為 java.lang.IntegerCache.high 系統(tǒng)屬性,然后 IntegerCache 初始化的時(shí)候就會(huì)讀取該系統(tǒng)屬性來(lái)決定上界。
下面是IntegerCache的源碼(其中紅色部分正是獲取系統(tǒng)屬性重新設(shè)置上界的邏輯)
1 private static class IntegerCache { 2 static final int low = -128; 3 static final int high; 4 static final Integer cache[]; 5 6 static { 7 // high value may be configured by property 8 int h = 127; 9 String integerCacheHighPropValue = 10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); 11 if (integerCacheHighPropValue != null) { 12 try { 13 int i = parseInt(integerCacheHighPropValue); 14 i = Math.max(i, 127); 15 // Maximum array size is Integer.MAX_VALUE 16 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); 17 } catch( NumberFormatException nfe) { 18 // If the property cannot be parsed into an int, ignore it. 19 } 20 } 21 high = h; 22 23 cache = new Integer[(high - low) + 1]; 24 int j = low; 25 for(int k = 0; k < cache.length; k++) 26 cache[k] = new Integer(j++); 27 28 // range [-128, 127] must be interned (JLS7 5.1.7) 29 assert IntegerCache.high >= 127; 30 } 31 32 private IntegerCache() {} 33 }
驗(yàn)證包裝類的緩存池(以Integer為例)
1 Integer x = new Integer(123); 2 Integer y = new Integer(123); 3 System.out.println(x == y); // false 4 Integer z = Integer.valueOf(123); 5 Integer k = Integer.valueOf(123); 6 System.out.println(z == k); // true
6、補(bǔ)充
基本類型與包裝類如何選擇:
- 內(nèi)存占用和性能:基本數(shù)據(jù)類型直接在棧中分配內(nèi)存,占用空間較少,性能更高。而包裝類是對(duì)象類型,需要在堆中分配內(nèi)存,GC管理,因此會(huì)稍微影響性能
- 使用場(chǎng)景:一般來(lái)說(shuō),在性能要求較高的代碼中,我們優(yōu)先使用基本數(shù)據(jù)類型。而在需要面向?qū)ο蟮膱?chǎng)景下(例如集合類中需要使用對(duì)象類型),我們會(huì)選擇包裝類。包裝類還提供了一些靜態(tài)方法和常量,比如Integer.parseInt()、Double.NaN等,這些方法和屬性是基本數(shù)據(jù)類型所不具備的
基本類型與包裝類==比較:
只要判斷中有基本數(shù)據(jù)類型,則判斷的就是值是否相等,也就是說(shuō)包裝類在這時(shí)會(huì)自動(dòng)拆箱。

1 public class Example { 2 public static void main(String[] args) { 3 Integer i1 = 10; 4 int i2 = 10; 5 6 System.out.println(i1 == i2); 7 } 8 }
基本類型與包裝類存儲(chǔ)區(qū)別(基本類型(primitive types)和包裝類的存儲(chǔ)位置取決于它們是在哪里聲明):
局部變量(Local Variables)
棧(Stack):如果基本類型是作為方法中的局部變量聲明的,那么它們會(huì)被存儲(chǔ)在棧中。棧用于管理方法調(diào)用和局部變量,具有后進(jìn)先出(LIFO)的行為。自動(dòng)分配和釋放:當(dāng)方法被調(diào)用時(shí),其局部變量會(huì)在棧上分配空間,并且在方法執(zhí)行完畢后自動(dòng)釋放。
成員變量(Instance Variables)
堆(Heap):如果基本類型是類的成員變量(即實(shí)例變量),那么它們會(huì)隨著對(duì)象一起被存儲(chǔ)在堆中。每個(gè)對(duì)象都有自己的成員變量副本,這些數(shù)據(jù)與對(duì)象本身一同存放在堆內(nèi)存中。生命周期依賴于對(duì)象:成員變量的生命周期與所屬的對(duì)象相同,只要對(duì)象存在,成員變量就存在;對(duì)象被垃圾回收時(shí),成員變量也會(huì)被回收。
靜態(tài)變量(Static Variables)
方法區(qū)/元空間(Method Area/Metaspace):靜態(tài)變量(無(wú)論是否為基本類型)屬于類的一部分,而不是某個(gè)特定的對(duì)象實(shí)例。它們通常存儲(chǔ)在方法區(qū)(Java 7及之前)或元空間(Java 8及之后)。不過(guò),在某些實(shí)現(xiàn)中,靜態(tài)變量也可能直接存放在堆中,因?yàn)榉椒▍^(qū)本身可以被視為堆的一部分。
共享性:靜態(tài)變量由所有對(duì)象實(shí)例共享,因此它們不是隨單個(gè)對(duì)象創(chuàng)建而創(chuàng)建,而是隨著類加載到 JVM 時(shí)初始化 。
代碼舉例:
1 public class Example { 2 // 成員變量,存儲(chǔ)在堆中 3 int instanceVar; 4 5 // 靜態(tài)變量,存儲(chǔ)在方法區(qū)或元空間 6 static int staticVar; 7 8 public void method() { 9 // 局部變量,存儲(chǔ)在棧中 10 int localVar = 10; 11 } 12 }

浙公網(wǎng)安備 33010602011771號(hào)