Java 包裝類型及易錯陷阱詳解
一、預備知識
1、Java把內(nèi)存劃分成兩種:一種是棧內(nèi)存,另一種是堆內(nèi)存。
2、int是基本類型,直接存數(shù)值;而 Integer是類,產(chǎn)生對象時用一個引用指向這個對象。
3、包裝器(wrapper)——這是《JAVA核心技術(shù)》一書中對Integer這類對象的稱呼。
4、包裝器位于java.lang包中。
5、包裝類是引用傳遞而基本類型是值傳遞(下面的內(nèi)存管理給予解釋)
6、基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配 ,而實際的對象是在存儲堆內(nèi)存中
int i = 5;//直接在棧中分配空間 Integer i = new Integr(5);//對象是在堆內(nèi)存中,而i(引用變量)是在棧內(nèi)存中
1.1 Java內(nèi)存管理
在函數(shù)中定義的一些基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配。當在一段代碼塊中定義一個變量時,Java就在棧中為這個變量分配內(nèi)存空間,當超過變量的作用域后,Java會自動釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立刻被另作他用。
堆內(nèi)存用于存放由new創(chuàng)建的對象和數(shù)組。在堆中分配的內(nèi)存,由Java虛擬機自動垃圾回收器來管理。在堆中產(chǎn)生了一個數(shù)組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數(shù)組或者對象在堆內(nèi)存中的首地址,在棧中的這個特殊的變量就變成了數(shù)組或者對象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對象,引用變量相當于為數(shù)組或者對象起的一個別名,或者代號。
引用變量是普通變量,定義時在棧中分配內(nèi)存,引用變量在程序運行到作用域外釋放。而數(shù)組&對象本身在堆中分配,即使程序運行到使用new產(chǎn)生數(shù)組和對象的語句所在地代碼塊之外,數(shù)組和對象本身占用的堆內(nèi)存也不會被釋放,數(shù)組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然占著內(nèi)存,在隨后的一個不確定的時間被垃圾回收器釋放掉。這也就是Java比較占內(nèi)存的主要原因,實際上棧中的變量指向堆內(nèi)存中的變量,這就是 Java 中的指針!
1.2 基本數(shù)據(jù)類型的包裝類
基本類型 | 包裝類 |
---|---|
boolean | Boolean |
char | Character |
byte | Byte |
int | integer |
long | Long |
float | Float |
double | Double |
short | Short |
1.3 包裝類的構(gòu)造方法
1、所有包裝類都可將與之對應的基本數(shù)據(jù)類型作為參數(shù),來構(gòu)造它們的實例
2、除Character類外,其他包裝類可將一個字符串作為參數(shù)構(gòu)造它們的實例
注意事項:
- Boolean類構(gòu)造方法參數(shù)為String類型時,若該字符串內(nèi)容為true(不考慮大小寫),則該Boolean對象表示true,否則表示false。
- 當Number包裝類構(gòu)造方法參數(shù)為String類型時,字符串不能為null,且該字符串必須可解析為相應的基本數(shù)據(jù)類型的數(shù)據(jù),否則編譯通過,運行時報NumberFormatException異常。
1.4 包裝類的優(yōu)缺點
包裝類優(yōu)點:
1、提供了一系列實用的方法
2、集合不允許存放基本數(shù)據(jù)類型數(shù)據(jù),存放數(shù)字時,要用包裝類型
包裝類缺點:
- 由于每個值分別包裝在對象中,所以ArrayList<Integer>的效率遠遠低于int[]數(shù)組。(應該用其構(gòu)造小型集合,其原因是程序員操作的方便性要比執(zhí)行效率更加重要)
1.5 包裝類易錯點
- 對象包裝器類是不可變的,即一旦構(gòu)造了包裝器,就不允許更改包裝在其中的值。
- 對象包裝器類是不可變的,因此不能定義他們的子類。
Integer i = new Integer(20); i = 50; System.out.println(i); // 50
疑問:為什么變了,前面說是不可變的咋變了,前后不是矛盾嗎?
想想前面介紹的Java內(nèi)存管理方式,也許你已經(jīng)明白了,如果還不明白就看看我的解釋:
Integer i 中的 i 只是棧中指向?qū)ο蟮囊粋€引用,后來 i = 50 又將i指向了50(此處運用到了自動裝箱技術(shù)),這就是變化的原因,但是原來堆中創(chuàng)建的對象還是不變的。
除了包裝器類型:Integer、Long、Short、Byte、Character、Boolean、Float和Double之外,還有BigInteger(java.math包)實例是不可變的,String、BigDecimal也是如此,不能修改它的值。不能修改現(xiàn)有實例的值,對這些類型的操作將返回新的實例。起先,不可變類型看起來可能很不自然,但是它具有很多勝過與其向?qū)目勺冾愋偷膬?yōu)勢。不可變類型更容易設(shè)計、實現(xiàn)和使用;它出錯的可能性更小,并且更加安全
為了在一個包含對不可變對象引用的變量上執(zhí)行計算,需要將計算的結(jié)果賦值給該變量。如下面的示例:
BigInteger fiveThousand = new BigInteger("5000"); BigInteger fiftyThousand = new BigInteger("50000"); BigInteger fiveHundredThousand = new BigInteger("500000"); BigInteger total = BigInteger.ZERO; total = total.add(fiveThousand); total = total.add(fiftyThousand); total = total.add(fiveHundredThousand); System.out.println(total);
二、自動拆/裝箱
基本數(shù)據(jù)(Primitive)類型的自動裝箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0開始提供的功能。
Java語言規(guī)范中說道:在許多情況下包裝與解包裝是由編譯器自行完成的(在這種情況下包裝稱為裝箱,解包裝稱為拆箱)。
//聲明一個Integer對象 Integer num = 10; /*以上的聲明就是用到了自動的裝箱,解析為: Integer num = new Integer(10); 以上就是一個很好的體現(xiàn),因為10是屬于基本數(shù)據(jù)類型的,原則上它是不能直接賦值給一個對象Integer的,但jdk1.5后你就可以進行這樣的聲明,這就是自動裝箱的魅力,自動將基本數(shù)據(jù)類型轉(zhuǎn)化為對應的封裝類型。成為一個對象以后就可以調(diào)用對象所聲明的所有的方法 自動拆箱:故名思議就是將對象重新轉(zhuǎn)化為基本數(shù)據(jù)類型:*/ //裝箱 Integer num_1 = 10; //拆箱 int num_2 = num_1; /*自動拆箱有個很典型的用法就是在進行運算的時候:因為對象時不能直接進行運算的,而是要轉(zhuǎn)化為基本數(shù)據(jù)類型后才能進行加減乘除*/ Integer num_3 = 10; //進行計算時隱含的有自動拆箱 System.out.print(num_3--); /*哈哈 應該感覺很簡單吧,下面我再來講點稍微難點的.*/ //在-128~127 之外的數(shù) Integer num_4 = 297; Integer num_5 = 297; System.out.println("num_4==num_5: "+(num_4==num_5)); // 在-128~127 之內(nèi)的數(shù) Integer num_6 = 97; Integer num_7 = 97; System.out.println("num_6==num_7: "+(num_6==num_7)); /*打印的結(jié)果是: num_4==num_5: false num_6==num_7: true */ //此處的解釋在下方
注意事項:
1、裝箱和拆箱是編譯器認可的,而不是虛擬機。編譯器在生成類的字節(jié)碼時,插入必要的方法調(diào)用。虛擬機只是執(zhí)行字節(jié)碼。
2、包裝對象和拆箱對象可以自由轉(zhuǎn)換,但是要剔除NULL值,因為null值并不能轉(zhuǎn)化為基本類型。
import java.util.ArrayList; import java.util.List; public class Ceshi { // 計算list中所有元素之和 public static int f(List<Integer> list) { int count = 0; for (int i : list) { count += i; } return count; } public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(null); System.out.println(f(list)); } }
運行結(jié)果:Exception in thread "main" java.lang.NullPointerException
運行失敗,報空指針異常,我們稍稍思考一下很快就知道原因了:在程序的for循環(huán)中,隱含了一個拆箱過程,再此過程中包裝類型轉(zhuǎn)換為了基本類型。我們知道拆箱過程是通過調(diào)用包裝對象的intValue方法來實現(xiàn)的,由于包裝對象是null值,訪問其intValue方法報空指針異常也就在所難免了。問題找到了,那就解決。(即加入null值檢查即可)
// 計算list中所有元素之和 public static int f(List<Integer> list) { int count = 0; for (Integer i : list) { count += (null != i) ? i : 0; } return count; }
針對此類問題:謹記包裝類型參與運算時,要做null值校驗。
三、整形池
@SuppressWarnings("resource") Scanner input = new Scanner(System.in); while (input.hasNextInt()) { int i = input.nextInt(); System.out.println("\n*********" + i + "的相等判斷**********"); // 兩個通過new產(chǎn)生的integer對象 Integer temp1 = new Integer(i); Integer temp2 = new Integer(i); System.out.println("new產(chǎn)生的對象:" + (temp1 == temp2)); // 基本類型轉(zhuǎn)為包裝類型后比較 temp1 = i; temp2 = i; System.out.println("基本類型轉(zhuǎn)換的對象:" + (temp1 == temp2)); // 通過靜態(tài)方法生成一個實例 temp1 = Integer.valueOf(i); temp2 = Integer.valueOf(i); System.out.println("valueOf產(chǎn)生的對象:" + (temp1 == temp2)); }
運行結(jié)果:
127 128 258
*********127的相等判斷**********
new產(chǎn)生的對象:false
基本類型轉(zhuǎn)換的對象:true
valueOf產(chǎn)生的對象:true
*********128的相等判斷**********
new產(chǎn)生的對象:false
基本類型轉(zhuǎn)換的對象:false
valueOf產(chǎn)生的對象:false
*********258的相等判斷**********
new產(chǎn)生的對象:false
基本類型轉(zhuǎn)換的對象:false
valueOf產(chǎn)生的對象:false
很不可思議,數(shù)字127的比較結(jié)果與另外兩個竟然不一樣,原因在哪里?
- new產(chǎn)生的Integer對象:new聲明的就是要生成一個新的對象,因為是兩個對象,地址肯定不一樣,所以比較結(jié)果為false毫無疑問。
- 裝箱生成的對象:首先說明一點,自動裝箱的動作是通過valueOf方法實現(xiàn)的,也就是說后兩個算法是相同的,所以他們的結(jié)果一樣,產(chǎn)生上面現(xiàn)象的原因是什么呢?
我們來看一下valueOf的源代碼:
/*** * Cache to support the object identity semantics of autoboxing for values * between*-128 and 127(inclusive)as required by JLS.**The cache is initialized * on first usage.The size of the cache*may be controlled by the{ * * @code -XX:AutoBoxCacheMax=<size>}option.*During VM * initialization,java.lang.Integer.IntegerCache.high property*may be set * and saved in the private system properties in the*sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) - 1); } catch (NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for (int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() { } } /** * Returns an {@code Integer} instance representing the specified * {@code int} value. If a new {@code Integer} instance is not * required, this method should generally be used in preference to * the constructor {@link #Integer(int)}, as this method is likely * to yield significantly better space and time performance by * caching frequently requested values. * * This method will always cache values in the range -128 to 127, * inclusive, and may cache other values outside of this range. * * @param i an {@code int} value. * @return an {@code Integer} instance representing {@code i}. * @since 1.5 */ public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
如果不是-128到127之間的 int 類型轉(zhuǎn)換為 Integer 對象,則直接返回一個新的對象。否則直接從cache數(shù)組中獲得。
cache是IntegerCache內(nèi)部類的一個靜態(tài)數(shù)組,容納的是-128到127之間的Integer對象。通過valueOf產(chǎn)生包裝對象時,如果int參數(shù)在-128到127之間,則直接從整型池中獲得對象,不在該范圍的int類型則通過new生成包裝對象。
這就是整形池,其存在不僅提高了系統(tǒng)性能,同時節(jié)約了內(nèi)存空間。
所以在聲明包裝對象的時候使用valueOf生成,而不是通過構(gòu)造函數(shù)來生成的原因,在判斷對象是否相等的時候,最好是用equals方法,避免使用==產(chǎn)生非預期結(jié)果。
注意:通過包裝類的valueOf生成包裝實例可以顯著提高空間和時間性能。
四、優(yōu)先選擇基本數(shù)據(jù)類型
包裝類型是一個類,它提供了諸如構(gòu)造方法、類型轉(zhuǎn)換、比較等非常實用的功能,而且自動裝箱(拆箱)更是如虎添翼,但是無論是從安全性、性能方面來說,還是從穩(wěn)定性方面來說,基本類型是首選方案。
public class Ceshi { public static void main(String[] args) { Ceshi temp=new Ceshi(); int a=500; //分別傳遞int類型和Integer類型 temp.f(a); temp.f(Integer.valueOf(a)); } public void f(long i){ System.out.println("基本類型參數(shù)的方法被調(diào)用"); } public void f(Long i){ System.out.println("包裝類型參數(shù)的方法被調(diào)用"); } }
上面程序的運行結(jié)果是:
基本類型參數(shù)的方法被調(diào)用
基本類型參數(shù)的方法被調(diào)用
很詫異是吧!感覺應該輸出不一樣的,第一個輸出毫無疑問,系統(tǒng)進行了自動的類型轉(zhuǎn)換,這種轉(zhuǎn)換只能往高提升。第二個為什么沒有調(diào)用包裝類參數(shù)的函數(shù)呢?
原因是自動裝箱有一個重要的原則:基本類型可以先加寬,再轉(zhuǎn)成寬類型的包裝類型,但不能直接轉(zhuǎn)變成寬類型的包裝類型。換句話說int可以加寬轉(zhuǎn)變成long,然后在轉(zhuǎn)變成Long對象,但不能直接轉(zhuǎn)變成包裝類型,注意這里指的都是自動轉(zhuǎn)換,不是通過構(gòu)造函數(shù)生成。
temp.f(Integer.valueOf(a));
這段代碼的執(zhí)行過程為
1、a 通過valueOf方法包裝成一個Integer對象。
2、由于沒有f(Integer i)方法,編譯器“聰明”地把 Integer 對象又轉(zhuǎn)換成 int。
3、int 自動拓寬為 long,編譯結(jié)束。
注意:基本數(shù)據(jù)類型優(yōu)先考慮。
public class Ceshi { public static void main(String[] args) { Ceshi temp=new Ceshi(); int a=500; //分別傳遞int類型和Long類型 temp.f(a); temp.f(Long.valueOf(a)); } public void f(long i){ System.out.println("基本類型參數(shù)的方法被調(diào)用"); } public void f(Long i){ System.out.println("包裝類型參數(shù)的方法被調(diào)用"); } }
這段程序的輸出結(jié)果為:
基本類型參數(shù)的方法被調(diào)用
包裝類型參數(shù)的方法被調(diào)用
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
被遺忘的Java關(guān)鍵字transient的使用詳解
在 Java 中,transient 是一個關(guān)鍵字,用于指定一個類的字段(成員變量)在序列化時應該被忽略。本文將通過示例為大家簡單講講transient的使用,需要的可以參考一下2023-04-04Java如何將字符串轉(zhuǎn)為數(shù)字int的三種方式詳析
這篇文章主要給大家介紹了關(guān)于Java如何將字符串轉(zhuǎn)為數(shù)字int的三種方式,在編程中我們經(jīng)常需要進行各種數(shù)據(jù)類型之間的轉(zhuǎn)換操作,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2023-10-10SpringCloud?集成Sentinel的實戰(zhàn)教程
這篇文章主要介紹了SpringCloud?集成Sentinel的詳細過程,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細,感興趣的朋友一起看看吧2024-08-08springboot layui hutool Excel導入的實現(xiàn)
本文主要介紹了springboot layui hutool Excel導入的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-03-03SSh結(jié)合Easyui實現(xiàn)Datagrid的分頁顯示
這篇文章主要為大家詳細介紹了SSh結(jié)合Easyui實現(xiàn)Datagrid的分頁顯示的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-06-06java實現(xiàn)Img與PDF相互轉(zhuǎn)換
這篇文章主要為大家詳細介紹了java實現(xiàn)Img與PDF相互轉(zhuǎn)換的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05