詳細(xì)說一說Java自動裝箱與拆箱是什么
一、核心概念:什么是裝箱與拆箱?
要理解“自動”,首先要理解手動的“裝箱”和“拆箱”。
Java 是一個面向?qū)ο蟮恼Z言,但為了效率,它同時包含了兩種不同的類型系統(tǒng):
基本數(shù)據(jù)類型:
byte,short,int,long,float,double,char,boolean。它們直接存儲“值”,存在于棧內(nèi)存中,效率高。引用類型:所有
Object的子類。它們存儲的是對象的“引用”(地址),實(shí)際對象存在于堆內(nèi)存中。
在某些場景下(例如使用集合類 ArrayList, HashMap),我們必須使用引用類型,因?yàn)榧现荒艽鎯ο?。這就產(chǎn)生了在基本類型和其對應(yīng)的包裝類對象之間轉(zhuǎn)換的需求。
包裝類:Java 為每一個基本類型都提供了一個對應(yīng)的“包裝類”,將這些基本類型“包裝”成對象。
| 基本數(shù)據(jù)類型 | 包裝類 |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
1. 手動裝箱
將一個基本數(shù)據(jù)類型的值,包裝成對應(yīng)的包裝類對象。
// 手動裝箱 int i = 10; Integer integerObj = Integer.valueOf(i); // 方式一:推薦,使用了緩存(后面會講) // 或者 Integer integerObj2 = new Integer(i); // 方式二:已過時 (Deprecated),不推薦
2. 手動拆箱
從一個包裝類對象中,提取出它所包裹的基本數(shù)據(jù)類型的值。
// 手動拆箱 Integer integerObj = new Integer(10); int j = integerObj.intValue(); // 從 Integer 對象中取出 int 值
二、什么是自動裝箱與拆箱?
從 Java 5 開始,為了簡化開發(fā),編譯器提供了自動裝箱和自動拆箱的功能。它本質(zhì)上是一種“語法糖”,編譯器在背后自動幫我們插入了轉(zhuǎn)換代碼,讓我們可以用更簡潔的方式編寫。
1. 自動裝箱
編譯器自動將基本數(shù)據(jù)類型轉(zhuǎn)換為對應(yīng)的包裝類對象。
// 自動裝箱 int i = 10; Integer integerObj = i; // 編譯器背后實(shí)際執(zhí)行的是:Integer integerObj = Integer.valueOf(i);
在這行代碼中,一個 int 類型的變量 i 被直接賦給了一個 Integer 類型的引用。編譯器在編譯時,會悄悄地調(diào)用 Integer.valueOf(i) 來完成轉(zhuǎn)換。
2. 自動拆箱
編譯器自動將包裝類對象轉(zhuǎn)換為對應(yīng)的基本數(shù)據(jù)類型。
// 自動拆箱 Integer integerObj = new Integer(10); int j = integerObj; // 編譯器背后實(shí)際執(zhí)行的是:int j = integerObj.intValue();
在這里,一個 Integer 對象被直接賦給了一個 int 類型的變量。編譯器在編譯時,會悄悄地調(diào)用 integerObj.intValue() 來完成轉(zhuǎn)換。
三、實(shí)際應(yīng)用場景舉例
自動裝箱和拆箱讓我們的代碼變得非常簡潔,尤其是在使用集合類時。
// 在 Java 5 之前,使用 ArrayList 非常麻煩 ArrayList list = new ArrayList(); list.add(Integer.valueOf(1)); // 手動裝箱 int value = (Integer) list.get(0)).intValue(); // 取出來是 Object,要強(qiáng)轉(zhuǎn),再手動拆箱 // 在 Java 5 之后,有了泛型和自動裝箱/拆箱 ArrayList<Integer> list = new ArrayList<>(); list.add(1); // 自動裝箱:int -> Integer int value = list.get(0); // 自動拆箱:Integer -> int。代碼清晰多了!
其他常見場景:
// 1. 方法調(diào)用時傳遞參數(shù)
public void processInteger(Integer i) { ... }
processInteger(5); // 自動裝箱,將 int 5 轉(zhuǎn)為 Integer
// 2. 運(yùn)算符運(yùn)算時
Integer a = 10;
Integer b = 20;
int c = a + b; // a 和 b 先自動拆箱為 int,相加后結(jié)果再自動裝箱賦給 Integer(如果接收類型是Integer)
// 等價于:int c = a.intValue() + b.intValue();
// 3. 三目運(yùn)算符中
boolean flag = true;
Integer i = flag ? 100 : 200; // 100和200都會被自動裝箱為Integer四、注意事項(xiàng)與陷阱(非常重要?。?/h2>
自動裝箱雖然方便,但也引入了一些容易忽略的陷阱。
1. 空指針異常
因?yàn)樽詣硬鹣鋵?shí)際上是調(diào)用了 xxxValue() 方法,如果包裝類對象是 null,調(diào)用方法就會拋出 NullPointerException。
Integer nullInteger = null; int i = nullInteger; // 運(yùn)行時拋出 NullPointerException! // 背后是:int i = nullInteger.intValue();
2. 性能消耗
雖然單次的裝箱/拆箱開銷很小,但在循環(huán)次數(shù)極多(例如上億次)的場景下,頻繁的創(chuàng)建和銷毀對象會帶來不必要的性能開銷。
long start = System.currentTimeMillis();
Long sum = 0L; // 這里用的是包裝類 Long,會觸發(fā)自動裝箱
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i; // 每次循環(huán):i自動裝箱為Long,然后sum拆箱為long,相加后再裝箱為Long
}
long end = System.currentTimeMillis();
System.out.println("耗時:" + (end - start));
// 將 Long sum = 0L 改為 long sum = 0L,性能會有巨大提升。3. 相等比較的陷阱
第一題:以下的代碼會輸出什么?
public class Main{
public static void main(Sring[] args){
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}運(yùn)行結(jié)果:
true
false
為什么會出現(xiàn)這樣的結(jié)果?輸出結(jié)果表明 i1 和 i2 指向的是同一個對象,而 i3 和 i4 指向的是不同的對象。此時只需一看源碼便知究竟,下面這段代碼是 Integer 的 valueOf 方法的具體實(shí)現(xiàn):
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}其中 IntegerCache 類的實(shí)現(xiàn)為:
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
} 在通過 valueOf 方法創(chuàng)建 Integer 對象的時候,如果數(shù)值在 [-128,127] 之間,便返回指向 IntegerCache.cache 中已經(jīng)存在的對象的引用;否則創(chuàng)建一個新的 Integer 對象。
上面的代碼中 i1 和 i2 的數(shù)值為100,因此會直接從 cache 中取已經(jīng)存在的對象,所以 i1 和 i2 指向的是同一個對象,而 i3 和 i4 則是分別指向不同的對象。
第二題:以下的代碼會輸出什么?
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}運(yùn)行結(jié)果:
false
false
原因: 在某個范圍內(nèi)的整型數(shù)值的個數(shù)是有限的,而浮點(diǎn)數(shù)卻不是。
4. 三目運(yùn)算符的陷阱
這是一個非常隱蔽的陷阱。
boolean flag = true; Integer i = flag ? 100 : Integer.valueOf(200); // 這沒問題,因?yàn)?100 和 Integer.valueOf(200) 類型“一致”(都是Integer對象) Integer i = flag ? 100 : null; // 當(dāng) flag 為 false 時,會發(fā)生什么? // 編譯器認(rèn)為 100 是 int,null 是 Integer。為了類型一致,它會將 100 自動裝箱為 Integer,將 null 作為 Integer。 // 所以這里不會報錯,i 會被賦值為 null。 int j = flag ? 100 : Integer.valueOf(200); // 這也沒問題,因?yàn)?Integer.valueOf(200) 會被自動拆箱為 int。 int k = flag ? 100 : null; // 當(dāng) flag 為 false 時,會發(fā)生 NullPointerException! // 因?yàn)榫幾g器需要得到一個 int 類型的結(jié)果,所以它會嘗試對 `null` 進(jìn)行自動拆箱,調(diào)用 null.intValue()。
總結(jié)
自動裝箱:
基本類型 -> 包裝類,編譯器調(diào)用valueOf()。自動拆箱:
包裝類 -> 基本類型,編譯器調(diào)用xxxValue()。優(yōu)點(diǎn):簡化代碼,使基本類型和包裝類之間的轉(zhuǎn)換無縫進(jìn)行。
陷阱:
空指針:包裝類為
null時拆箱會拋NPE。性能:在密集循環(huán)中可能帶來開銷。
比較:
==比較包裝類是比較地址,應(yīng)使用equals或先拆箱。三目運(yùn)算符:要注意類型的統(tǒng)一,避免意外的自動拆箱。
到此這篇關(guān)于Java自動裝箱與拆箱是什么的文章就介紹到這了,更多相關(guān)Java自動裝箱與拆箱內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot的listener(監(jiān)聽器)簡單使用實(shí)例詳解
監(jiān)聽器(Listener)的注冊方法和 Servlet 一樣,有兩種方式:代碼注冊或者注解注冊。接下來通過本文給大家介紹Spring Boot的listener(監(jiān)聽器)簡單使用,需要的朋友可以參考下2017-04-04
Java實(shí)現(xiàn)的properties文件動態(tài)修改并自動保存工具類
這篇文章主要介紹了Java實(shí)現(xiàn)的properties文件動態(tài)修改并自動保存工具類,可實(shí)現(xiàn)針對properties配置文件的相關(guān)修改與保存功能,需要的朋友可以參考下2017-11-11
SpringMVC對自定義controller入?yún)㈩A(yù)處理方式
這篇文章主要介紹了SpringMVC對自定義controller入?yún)㈩A(yù)處理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
生產(chǎn)環(huán)境NoHttpResponseException異常排查解決記錄分析
這篇文章主要為大家介紹了生產(chǎn)環(huán)境NoHttpResponseException異常排查解決記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
Java正則表達(dá)式Pattern和Matcher原理詳解
這篇文章主要介紹了Java正則表達(dá)式Pattern和Matcher原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-02-02
java監(jiān)聽器實(shí)現(xiàn)在線人數(shù)統(tǒng)計
這篇文章主要為大家詳細(xì)介紹了java監(jiān)聽器實(shí)現(xiàn)在線人數(shù)統(tǒng)計,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11
SpringBoot的HandlerInterceptor中依賴注入為null問題
這篇文章主要介紹了SpringBoot的HandlerInterceptor中依賴注入為null問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
徹底搞明白Spring中的自動裝配和Autowired注解的使用
這篇文章主要介紹了徹底搞明白Spring中的自動裝配和Autowired注解的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-03-03

