日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一文讀懂什么是Java中的自動拆裝箱

 本文主要介紹Java中的自動拆箱與自動裝箱的有關知識。

基本數(shù)據(jù)類型

基本類型,或者叫做內置類型,是Java中不同于類(Class)的特殊類型。它們是我們編程中使用最頻繁的類型。

Java是一種強類型語言,***次申明變量必須說明數(shù)據(jù)類型,***次變量賦值稱為變量的初始化。

Java基本類型共有八種,基本類型可以分為三類:

  • 字符類型char
  • 布爾類型boolean
  • 整數(shù)類型byte、short、int、long
  • 浮點數(shù)類型float、double。

Java中的數(shù)值類型不存在無符號的,它們的取值范圍是固定的,不會隨著機器硬件環(huán)境或者操作系統(tǒng)的改變而改變。

實際上,Java中還存在另外一種基本類型void,它也有對應的包裝類 java.lang.Void,不過我們無法直接對它們進行操作。

基本數(shù)據(jù)類型有什么好處

我們都知道在Java語言中,new一個對象是存儲在堆里的,我們通過棧中的引用來使用這些對象;所以,對象本身來說是比較消耗資源的。

對于經(jīng)常用到的類型,如int等,如果我們每次使用這種變量的時候都需要new一個Java對象的話,就會比較笨重。

所以,和C++一樣,Java提供了基本數(shù)據(jù)類型,這種數(shù)據(jù)的變量不需要使用new創(chuàng)建,他們不會在堆上創(chuàng)建,而是直接在棧內存中存儲,因此會更加高效。

整型的取值范圍

Java中的整型主要包含byte、short、int和long這四種,表示的數(shù)字范圍也是從小到大的,之所以表示范圍不同主要和他們存儲數(shù)據(jù)時所占的字節(jié)數(shù)有關。

先來個簡答的科普,1字節(jié)=8位(bit)。java中的整型屬于有符號數(shù)。

先來看計算中8bit可以表示的數(shù)字:

 
 
 
 
  1. 最小值:10000000 (-128)(-2^7)
  2. ***值:01111111(127)(2^7-1)

整型的這幾個類型中,

  • byte:byte用1個字節(jié)來存儲,范圍為-128(-2^7)到127(2^7-1),在變量初始化的時候,byte類型的默認值為0。
  • short:short用2個字節(jié)存儲,范圍為-32,768 (-2^15)到32,767 (2^15-1),在變量初始化的時候,short類型的默認值為0,一般情況下,因為Java本身轉型的原因,可以直接寫為0。
  • int:int用4個字節(jié)存儲,范圍為-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在變量初始化的時候,int類型的默認值為0。
  • long:long用8個字節(jié)存儲,范圍為-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在變量初始化的時候,long類型的默認值為0L或0l,也可直接寫為0。

超出范圍怎么辦

上面說過了,整型中,每個類型都有一定的表示范圍,但是,在程序中有些計算會導致超出表示范圍,即溢出。如以下代碼:

 
 
 
 
  1. int i = Integer.MAX_VALUE;
  2. int j = Integer.MAX_VALUE;
  3. int k = i + j;
  4. System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")");

輸出結果:i (2147483647) + j (2147483647) = k (-2)

這就是發(fā)生了溢出,溢出的時候并不會拋異常,也沒有任何提示。所以,在程序中,使用同類型的數(shù)據(jù)進行運算的時候,一定要注意數(shù)據(jù)溢出的問題。

包裝類型

Java語言是一個面向對象的語言,但是Java中的基本數(shù)據(jù)類型卻是不面向對象的,這在實際使用時存在很多的不便,為了解決這個不足,在設計類時為每個基本數(shù)據(jù)類型設計了一個對應的類進行代表,這樣八個和基本數(shù)據(jù)類型對應的類統(tǒng)稱為包裝類(Wrapper Class)。

包裝類均位于java.lang包,包裝類和基本數(shù)據(jù)類型的對應關系如下表所示

在這八個類名中,除了Integer和Character類以后,其它六個類的類名和基本數(shù)據(jù)類型一致,只是類名的***個字母大寫即可。

為什么需要包裝類

很多人會有疑問,既然Java中為了提高效率,提供了八種基本數(shù)據(jù)類型,為什么還要提供包裝類呢?

這個問題,其實前面已經(jīng)有了答案,因為Java是一種面向對象語言,很多地方都需要使用對象而不是基本數(shù)據(jù)類型。比如,在集合類中,我們是無法將int 、double等類型放進去的。因為集合的容器要求元素是Object類型。

為了讓基本類型也具有對象的特征,就出現(xiàn)了包裝類型,它相當于將基本類型“包裝起來”,使得它具有了對象的性質,并且為其添加了屬性和方法,豐富了基本類型的操作。

拆箱與裝箱

那么,有了基本數(shù)據(jù)類型和包裝類,肯定有些時候要在他們之間進行轉換。比如把一個基本數(shù)據(jù)類型的int轉換成一個包裝類型的Integer對象。

我們認為包裝類是對基本類型的包裝,所以,把基本數(shù)據(jù)類型轉換成包裝類的過程就是打包裝,英文對應于boxing,中文翻譯為裝箱。

反之,把包裝類轉換成基本數(shù)據(jù)類型的過程就是拆包裝,英文對應于unboxing,中文翻譯為拆箱。

在Java SE5之前,要進行裝箱,可以通過以下代碼:

 
 
 
 
  1. Integer i = new Integer(10);

自動拆箱與自動裝箱

在Java SE5中,為了減少開發(fā)人員的工作,Java提供了自動拆箱與自動裝箱功能。

自動裝箱: 就是將基本數(shù)據(jù)類型自動轉換成對應的包裝類。

自動拆箱:就是將包裝類自動轉換成對應的基本數(shù)據(jù)類型。

 
 
 
 
  1. Integer i =10;  //自動裝箱
  2. int b= i;     //自動拆箱

Integer i=10 可以替代 Integer i = new Integer(10);,這就是因為Java幫我們提供了自動裝箱的功能,不需要開發(fā)者手動去new一個Integer對象。

自動裝箱與自動拆箱的實現(xiàn)原理

既然Java提供了自動拆裝箱的能力,那么,我們就來看一下,到底是什么原理,Java是如何實現(xiàn)的自動拆裝箱功能。

我們有以下自動拆裝箱的代碼:

 
 
 
 
  1. public static  void main(String[]args){
  2.     Integer integer=1; //裝箱
  3.     int i=integer; //拆箱
  4. }

對以上代碼進行反編譯后可以得到以下代碼:

 
 
 
 
  1. public static  void main(String[]args){
  2.     Integer integer=Integer.valueOf(1); 
  3.     int i=integer.intValue(); 
  4. }

從上面反編譯后的代碼可以看出,int的自動裝箱都是通過Integer.valueOf()方法來實現(xiàn)的,Integer的自動拆箱都是通過integer.intValue來實現(xiàn)的。如果讀者感興趣,可以試著將八種類型都反編譯一遍 ,你會發(fā)現(xiàn)以下規(guī)律:

自動裝箱都是通過包裝類的valueOf()方法來實現(xiàn)的.自動拆箱都是通過包裝類對象的xxxValue()來實現(xiàn)的。

哪些地方會自動拆裝箱

我們了解過原理之后,在來看一下,什么情況下,Java會幫我們進行自動拆裝箱。前面提到的變量的初始化和賦值的場景就不介紹了,那是最簡單的也最容易理解的。

我們主要來看一下,那些可能被忽略的場景。

場景一、將基本數(shù)據(jù)類型放入集合類

我們知道,Java中的集合類只能接收對象類型,那么以下代碼為什么會不報錯呢?

 
 
 
 
  1. List li = new ArrayList<>();
  2. for (int i = 1; i < 50; i ++){
  3.     li.add(i);
  4. }

將上面代碼進行反編譯,可以得到以下代碼:

 
 
 
 
  1. List li = new ArrayList<>();
  2. for (int i = 1; i < 50; i += 2){
  3.     li.add(Integer.valueOf(i));
  4. }

以上,我們可以得出結論,當我們把基本數(shù)據(jù)類型放入集合類中的時候,會進行自動裝箱。

場景二、包裝類型和基本類型的大小比較

有沒有人想過,當我們對Integer對象與基本類型進行大小比較的時候,實際上比較的是什么內容呢?看以下代碼:

 
 
 
 
  1. Integer a=1;
  2. System.out.println(a==1?"等于":"不等于");
  3. Boolean bool=false;
  4. System.out.println(bool?"真":"假");

對以上代碼進行反編譯,得到以下代碼:

 
 
 
 
  1. Integer a=1;
  2. System.out.println(a.intValue()==1?"等于":"不等于");
  3. Boolean bool=false;
  4. System.out.println(bool.booleanValue?"真":"假");

可以看到,包裝類與基本數(shù)據(jù)類型進行比較運算,是先將包裝類進行拆箱成基本數(shù)據(jù)類型,然后進行比較的。

場景三、包裝類型的運算

有沒有人想過,當我們對Integer對象進行四則運算的時候,是如何進行的呢?看以下代碼:

 
 
 
 
  1. Integer i = 10;
  2. Integer j = 20;
  3. System.out.println(i+j);

反編譯后代碼如下:

 
 
 
 
  1. Integer i = Integer.valueOf(10);
  2. Integer j = Integer.valueOf(20);
  3. System.out.println(i.intValue() + j.intValue());

我們發(fā)現(xiàn),兩個包裝類型之間的運算,會被自動拆箱成基本類型進行。

場景四、三目運算符的使用

這是很多人不知道的一個場景,作者也是一次線上的血淋淋的Bug發(fā)生后才了解到的一種案例??匆粋€簡單的三目運算符的代碼:

 
 
 
 
  1. boolean flag = true;
  2. Integer i = 0;
  3. int j = 1;
  4. int k = flag ? i : j;

很多人不知道,其實在int k = flag ? i : j;這一行,會發(fā)生自動拆箱。反編譯后代碼如下:

 
 
 
 
  1. boolean flag = true;
  2. Integer i = Integer.valueOf(0);
  3. int j = 1;
  4. int k = flag ? i.intValue() : j;

這其實是三目運算符的語法規(guī)范:當?shù)诙?,第三位操作?shù)分別為基本類型和對象時,其中的對象就會拆箱為基本類型進行操作。

因為例子中,flag ? i : j;片段中,第二段的i是一個包裝類型的對象,而第三段的j是一個基本類型,所以會對包裝類進行自動拆箱。如果這個時候i的值為null,那么久會發(fā)生NPE。(自動拆箱導致空指針異常)

場景五、函數(shù)參數(shù)與返回值

這個比較容易理解,直接上代碼了:

 
 
 
 
  1. //自動拆箱
  2. public int getNum1(Integer num) {
  3.  return num;
  4. }
  5. //自動裝箱
  6. public Integer getNum2(int num) {
  7.  return num;
  8. }

自動拆裝箱與緩存

Java SE的自動拆裝箱還提供了一個和緩存有關的功能,我們先來看以下代碼,猜測一下輸出結果:

 
 
 
 
  1. public static void main(String... strings) {
  2.     Integer integer1 = 3;
  3.     Integer integer2 = 3;
  4.     if (integer1 == integer2)
  5.         System.out.println("integer1 == integer2");
  6.     else
  7.         System.out.println("integer1 != integer2");
  8.     Integer integer3 = 300;
  9.     Integer integer4 = 300;
  10.     if (integer3 == integer4)
  11.         System.out.println("integer3 == integer4");
  12.     else
  13.         System.out.println("integer3 != integer4");
  14. }

我們普遍認為上面的兩個判斷的結果都是false。雖然比較的值是相等的,但是由于比較的是對象,而對象的引用不一樣,所以會認為兩個if判斷都是false的。

在Java中,==比較的是對象應用,而equals比較的是值。

所以,在這個例子中,不同的對象有不同的引用,所以在進行比較的時候都將返回false。奇怪的是,這里兩個類似的if條件判斷返回不同的布爾值。

上面這段代碼真正的輸出結果:

 
 
 
 
  1. integer1 == integer2
  2. integer3 != integer4

原因就和Integer中的緩存機制有關。在Java 5中,在Integer的操作上引入了一個新功能來節(jié)省內存和提高性能。整型對象通過使用相同的對象引用實現(xiàn)了緩存和重用。

適用于整數(shù)值區(qū)間-128 至 +127。

只適用于自動裝箱。使用構造函數(shù)創(chuàng)建對象不適用。

具體的代碼實現(xiàn)可以閱讀Java中整型的緩存機制一文,這里不再闡述。

我們只需要知道,當需要進行自動裝箱時,如果數(shù)字在-128至127之間時,會直接使用緩存中的對象,而不是重新創(chuàng)建一個對象。

其中的javadoc詳細的說明了緩存支持-128到127之間的自動裝箱過程。***值127可以通過-XX:AutoBoxCacheMax=size修改。

實際上這個功能在Java 5中引入的時候,范圍是固定的-128 至 +127。后來在Java 6中,可以通過java.lang.Integer.IntegerCache.high設置***值。

這使我們可以根據(jù)應用程序的實際情況靈活地調整來提高性能。到底是什么原因選擇這個-128到127范圍呢?因為這個范圍的數(shù)字是最被廣泛使用的。 在程序中,***次使用Integer的時候也需要一定的額外時間來初始化這個緩存。

在Boxing Conversion部分的Java語言規(guī)范(JLS)規(guī)定如下:

如果一個變量p的值是:

 
 
 
 
  1. -128至127之間的整數(shù)(§3.10.1)
  2. true 和 false的布爾值 (§3.10.3)
  3. ‘\u0000’至 ‘\u007f’之間的字符(§3.10.4)

范圍內的時,將p包裝成a和b兩個對象時,可以直接使用a==b判斷a和b的值是否相等。

自動拆裝箱帶來的問題

當然,自動拆裝箱是一個很好的功能,大大節(jié)省了開發(fā)人員的精力,不再需要關心到底什么時候需要拆裝箱。但是,他也會引入一些問題。

包裝對象的數(shù)值比較,不能簡單的使用==,雖然-128到127之間的數(shù)字可以,但是這個范圍之外還是需要使用equals比較。

前面提到,有些場景會進行自動拆裝箱,同時也說過,由于自動拆箱,如果包裝類對象為null,那么自動拆箱時就有可能拋出NPE。

如果一個for循環(huán)中有大量拆裝箱操作,會浪費很多資源。

【本文是專欄作者Hollis的原創(chuàng)文章,作者微信公眾號Hollis(ID:hollischuang)】


本文名稱:一文讀懂什么是Java中的自動拆裝箱
分享網(wǎng)址:http://www.dlmjj.cn/article/cceppci.html