1. 程式人生 > >自動拆箱&自動裝箱以及String 和基本資料型別封裝類生成的物件是否相等

自動拆箱&自動裝箱以及String 和基本資料型別封裝類生成的物件是否相等

自動拆箱(unboxing)&自動裝箱(boxing)

@author 李東秀|| qq:1028659927

本文主要為自己理解所做的學習筆記,如有不對的地方,

望各位看官不吝指出,程式碼執行環境:Ubuntu 14.04,jdk1.7版本

        在jdk 1.5之前,如果你想要定義一個value100Integer物件,則需要如下定義:

          Integer i=new Integer (100);

     但有了自動拆裝箱之後就可以直接把基本型別賦值給Integer物件。

int intNum1=100;//普通常量
Integer intNum2=intNum1;//自動裝箱
int intNum3=intNum2;//自動拆箱
Integer intNum4=100;//自動裝箱

         上面的程式碼中,intNum2為一個Integer型別的例項,intNum1為Java中的基礎資料型別

intNum1賦值給intNum2便是自動裝箱;而將intNum2賦值給intNum3則是自動拆箱。

Java為我們提供了八種基本資料型別: boolean  byte  char  shrot  int  long  float  double ,所生成的變數相當於常量;

對應基本型別包裝類:Boolean Byte Character Short Integer Long Float Double。

自動拆箱和自動裝箱定義:

自動裝箱是將一個java定義的基本資料型別賦值給相應封裝類的變數。

拆箱與裝箱是相反的操作,自動拆箱則是將一個封裝類的變數賦值給相應基本資料型別的變數。

自動封箱和自動裝箱的原理

      自動封箱和自動拆箱是java自動幫我們完成的,通過除錯可以看到執行過程,如果除錯過程中無法進入原始碼中的函式,可能因為設定的是JRE執行環境,替換成jdk就可以了,Window-Preference-java-Installed JREs



int intNum1=100;//普通常量
Integer intNum2=intNum1;//自動裝箱
int intNum3=intNum2;//自動拆箱
Integer intNum4=100;//自動裝箱

在每一行程式碼新增斷點,進入除錯模式,

第一行程式碼無法進入函式,說明中間沒有呼叫其他的函式,

第二行程式碼和第四行程式碼進入了Integer.valueOf(int);

第三行程式碼進入了函式Integer.intValue();

第二行程式碼進入如下程式碼:

 public static Integer valueOf(int i) {
  //對於Integer型別變數,如果i在IntegerCache.low-IntegerCache.low之間,
//就直接在快取中取出i的Integer型別物件  ,而不需要生成新的物件。
        if (i >= IntegerCache.low && i <= IntegerCache.low
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);//否則在堆上建立新的物件
    }

此程式碼會去判斷是否在可快取的範圍內(if (i >= IntegerCache.low && i <= IntegerCache.high)),如果滿足快取條件進入return IntegerCache.cache[i + (-IntegerCache.low)];否則new 一個新的物件。所以可以看到IntegerintNum2=intNum1;並不是每次都會生成新物件。

 第三行進入intValue函式直接返回基本型別。

 public int intValue() {
        return value;
}//其他型別則會呼叫xxValue()

下面是快取資料的程式碼,說明了快取的範圍

 /**
     * 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() {}
    }

注意此處程式碼:

 String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");

說明integerCacheHighPropValue的大小是從VM虛擬機器中儲存的屬性取到的,

所以integerCacheHighPropValue 是可以通過vm命令進行設定的。

可以通過調整虛擬機器-XX:AutoBoxCacheMax=<size>選項,調整“自動裝箱池”的大小 ,

具體設定方法需要設定虛擬機器模式,這裡不做講解。

自動封箱&自動拆箱的使用場景:

1賦值過程中:

此過程不在舉例

2函式引數:

publicstaticintsubFun(Integer num){

returnnum;

}

呼叫此函式就會發生自動拆箱

publicstatic Integer subFun(intnum){

return num;

}

上述兩種場景在工作過程中最常見,另外常見的自動拆箱和裝箱的場景就是集合操作,集合只能接收物件,但我們傳入普通型別也是可以的,它內部就完成了自動裝箱。

特別需要注意地方:

1、 是否生成新的物件:

	/**
		 * 基本型別的常量池說明(-127-128)
		 * @author 李東秀(1028659927)
		 */
Integer num1 = 100; 
Integer num2 = 100;  
System.out.println("num1==num2: " + (num1 == num2));// true  兩個自動裝箱的物件,都是對常量池中物件的引用,所以相同。

 Integer num3 = 200;  
Integer num4 = 200;  
System.out.println("num3>num4: " + (num3 > num4)); // false 將兩個物件拆箱,再比較大小  ,類似的操作還有加減乘除取餘等

 Integer num5 = new Integer(100);  
Integer num6 = new Integer(100);  
System.out.println("num5==num6: " + (num5 == num6)); // false 都會生成新的物件,無論與誰比較都是false  

int intNum1 = 100;  
System.out.println("num1==int1: " + (num1 == intNum1));// true  Integer快取物件拆箱後與int比較 ,已經不是物件地址的比較

 int intNum2 = 200;  
System.out.println("num3==intNum2: " + (num3 == intNum2));// true  Integer物件拆箱後與int比較  

所以new物件的方式一定會產生新物件,但自動裝箱的方式則不一定產生新物件,對於Integer物件和基本物件進行運算操作或者邏輯操作,則會首先進行拆箱,之後進行值大小的比較。非new方式生成的Integer這裡用快取解釋,深層的原因是jvm方法區的常量池的存在,快取的物件大小-128-127,基本型別中Byte快取大小為(-128-127),Long快取大小為()等:

java中基本型別的包裝類的大部分都實現了常量池技術,這些類是Byte,Short,Integer,Long,Character,Boolean,另外兩種浮點數型別的包裝類則沒有實現。Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對應值小於等於127時才可使用常量池,也即物件不負責建立和管理大於127的這些類的物件。  

其它基本資料型別對應的包裝型別的自動裝箱池大小(可以在各個型別的快取類中找到範圍):

  • Boolean :全部快取
  • Byte :全部快取
  • Character : <=127快取
  • Short : (-128,127)快取
  • Long : (-128,127)快取
  • Float : (沒有快取)
  • Double : (沒有快取)
//5種整形的包裝類Byte,Short,Integer,Long,Character的物件,
		//在值小於127時可以使用常量池
		Integer intdemo1=127;
		Integer intdemo2=127;
		System.out.println(intdemo1==intdemo2); //輸出true
		//值大於127時,不會從常量池中取物件
		Integer intdemo3=128;
		Integer intdemo4=128;
		Byte bdemo1=12;
		Byte bdemo2=12;
		System.out.println(bdemo1==bdemo2); //輸出true
		Character chdemo1='a';//a=97
		Character chdemo2='a';
		System.out.println(chdemo1==chdemo2);//true
		//.......整形包裝類後面就不在實驗
		System.out.println(intdemo3==intdemo4); //輸出false
		//Boolean類也實現了常量池技術
		Boolean booldemo1=true;
		Boolean booldemo2=true;
		System.out.println(booldemo1==booldemo2); //輸出true
		//浮點型別的包裝類沒有實現常量池技術
		Double ddemo1=1.0;
		Double ddemo2=1.0;
		System.out.println(ddemo1==ddemo2); //輸出false

Java利用記憶體中儲存拘留字串也為String變數提供了快取特性。


	Integer I1 = 20;
		Integer I2 = 20;
		Integer I3 = 0;
		Integer I4 = 129;
		Integer I5 = 129;
		Integer I6 = new Integer("20");
		Integer I7 = new Integer("20");
		Integer I8 = new Integer("0");
		// ==
		System.out.println(I1 == I2);// 因為小於127所以都是引用的常量池彙總物件,相同
		System.out.println(I1 == I2 + I3);// 拆箱,不存在新物件的產生
		System.out.println(I4 == I5);// 無法存入常量池,會生成新的物件
		System.out.println(I4 == I5 + I3);// 拆箱比較普通值
		System.out.println(I1 == I6);// 生成新物件
		System.out.println(I6 == I7);// 生成新物件
		System.out.println(I6 == I7 + I8);//
		System.out.println("================");
		// equals比較值是否相同
		System.out.println(I1.equals(I2));
		System.out.println(I1.equals(I2 + I3));
		System.out.println(I4.equals(I5));
		System.out.println(I4.equals(I5 + I3));
		System.out.println(I6.equals(I7));
		System.out.println(I6.equals(I7 + I8));

		/**
		 * String 型別常量池
		 */
		System.out.println("================");
		String str1 = "ab";
		String str2 = "ab";
		String str3 = "cd";
		String str6 = "abcd";
		String str4 = "a";
		String str5 = "bcd";

		System.out.println(str1 == str2);// 證明常量池存在沒有生成新的物件
		System.out.println(str6 == str1 + str2);// 沒有拆箱過程所以不存在常量池的常量直接比較
		System.out.println(str6 == "abcd");//
		// 涉及到變數(不全是常量)的相加,所以會生成新的物件,其內部實現是先new一個StringBuilder
		System.out.println(str2 + str3 == str4 + str5);// 變數的改變,涉及到新建變數
		System.out.println(str6.equals(str2 + str3));
		// 下面重新定義,因為常量池中已存在abcd

		String str10 = new String("AB");
		String str11 = new String("AB");
		System.out.println("================");
		System.out.println(str10 == str11);
		String str7 = "AB";
		System.out.println(str10 == str7);
		String str8 = "CD";
		String str9 = "ABCD";
		System.out.println(str9 == str7 + str8);
		System.out.println(str9 == str10 + str11);

		String str12 = new String("CD");// 生成新的物件,CD 放入常量池
		String str13 = new String("ABCD");
		System.out.println(str9 == str13);
		System.out.println(str13 == str11 + str12);
		System.out.println(str13 == "ABCD");
		String str14 = new String("ABC") + new String("DEF");// 形成的ABCDEF不會被放入常量池
		str14.intern();// 新增進常量池中(快取)
		String str15 = "ABCDEF";
		System.out.println(str14 == str15);
		String str16 = new String("AAA");// AAA會被放入常量池中,由於是new產生的物件多以str16不和任何物件相同
		String str17 = "AAA";
		System.out.println(str16 == str17);

結果:true true false true false false true
================
true true true true true true
================
true false true false true
================
False false false false false false false true false

      除了八種基本型別還有java提供的一種比較特殊的型別String(複合)。Java也為它提供了快取機制,原始碼中所有相同字面值的字串常量只可能建立唯一 一個拘留字串物件。 實際上JVM是通過一個記錄了拘留字串引用的內部資料結構來維持這一特性的。在Java程式中,可以呼叫String的intern()方法來使得一個常規字串物件成為拘留字串物件。

        String主要使用方法有兩種:如果是使用引號宣告的字串都是會直接在字串常量池中生成,而 new 出來的 String 物件是放在 JAVA Heap 區域。

      提到此處就必須得提String.intern方法,intern方法的作用如下:直接使用雙引號宣告出來的String物件會直接儲存在常量池中,如果不是用雙引號宣告的String物件,可以使用String提供的intern方法。intern 方法會從字串常量池中查詢當前字串是否存在,若不存在就會將當前字串放入常量池中。

String s = new String("A");

會建立兩個物件一個“A”儲存在常量池中,另外一個建立在堆上,由於兩個物件所在位置就不相同。所以一定是不相同的。

 String s2 = new String("B") + new String("C");//此種方式建立的s2不會被放在常量池中,可以呼叫intern()方法把s2放入常量池中

Jdk1.7中:

String str14=new String("ABC")+new String("DEF");//此種方式生成的str14不會被放在常量池中

String str15="ABCDEF";

System.out.println(str14==str15);

結果:false

String str14=new String("ABC")+new String("DEF");

str14.intern();

String str15="ABCDEF";

System.out.println(str14==str15);

結果:true

看過jvm執行時記憶體分配的都知道,Intern方法的使用在Jdk7中和jdk6中稍有不同:

String常量池從Perm區移動到了Java Heap,所以呼叫intern 方法時,如果存在堆中的物件,會直接儲存物件的引用,而不會重新建立物件。(這部分內容還正在看,由於工作比較忙,最近剛開始寫blog,會在後面逐漸寫一系列文章)


2  Java函式過載

過載的定義為:函式引數型別不同或者引數個數不同則說明兩函式過載。

但現在存在自動裝箱,自動拆箱功能是否導致過載的函式為一個函式。

public static int add(Integer num){

return num;

}

public static int add(int num){

return num;

}


這樣定義是可以的,使用時相應引數傳進函式也不存在混淆問題,所以過載不會受到影響。


3 定義的物件可以為空,但是當自動拆箱賦值給普通型別時或者和基本型別比較時,則會報錯誤。

Integer num1=null;

int num2;

num2=num1;

System.out.println(num2);//或者比較也會發生此類錯誤


程式碼能過通過編譯,但執行會報java.lang.NullPointerException錯誤。

4 迴圈過程的重複物件建立

Integer num4=0;

for(inti=0;i<10;i++){

num4=num4+i;

}

這麼簡單的一段程式,打斷點檢視迴圈過程,會發現每次迴圈都要先拆箱再裝箱,裝箱過程中會生成新的物件,所以會生成很多無用的中間物件,降低程式的效能並且加重了垃圾回收的工作量。因此在我們程式設計時,需要注意到這一點,正確地宣告變數型別,避免因為自動裝箱引起的效能問題。


總結:

自動拆箱和自動裝箱給變成帶來了很多便利,但同時也存在許多弊端,java致力於不讓程式設計者擔心記憶體的分配,由垃圾收集器自動完成記憶體的管理,這不意味著我們可以隨意的使用記憶體而不加以限制。變成過程中儘量避免可能多次發生的拆箱裝箱操作,避免不必要物件的建立。(QQ:1028659927,歡迎指導!)


相關推薦

自動&自動裝箱以及String 基本資料型別封裝生成物件是否相等

自動拆箱(unboxing)&自動裝箱(boxing) @author 李東秀|| qq:1028659927 本文主要為自己理解所做的學習筆記,如有不對的地方, 望各位看官不吝指出,程式碼執行環境:Ubuntu 14.04,jdk1.7版本         在j

Java String基本資料型別的相互轉換

1.String->基本資料型別 int:Integer.parseInt(Str) double:Double.parseDouble(Str) float:Float.parseFloat(Str) byte:Byte.parseByte(Str) long:Long.

String基本資料型別包裝、集合泛型

String類: · 字串是一個特殊的物件,在java中只要被雙引號引起來的都是字串物件 · 字串一旦初始化就不可以被改變 · String類複寫了Object類中的equals方法,該用法用於判斷字串是否相同 · String s1 = "abc" 和 String s

String基本資料型別包裝

-----------android培訓、java培訓、java學習型技術部落格、期待與您交流!------------  第一講     String類 一、概述         String是字

java中的自動裝箱是指什麼?

JAVA語言中有個名詞叫自動拆箱、裝箱,那這個自動拆箱、裝箱到底是指啥? 自動拆箱、裝箱是從JDK1.5開始才有的特性,其實它主要就是指基本型別與包裝類的自動轉換。 如int 與Integer型別。 int 是基本型別,而Integer是int的包裝類,在

js教程--從入門到精通 第一篇 js的前世今生以及js中基本資料型別引入方式

1、Javascript前世今生    1.1、什麼是Javascript       Javascript運行於Javascript 【直譯器/引擎】中的解釋性指令碼語言     &nb

易學筆記-go語言-第4章:基本結構基本資料型別/4.4 變數/4.4.3 函式體內最簡單的變數初始化

函式體內最簡單的變數賦值 格式:  變數名 := 值 舉例: var goos string = os.Getenv("GOOS") fmt.Printf("The operating system is: %s\n", goos) //函式體內最

易學筆記-go語言-第4章:基本結構基本資料型別/4.4 變數/4.4.2 宣告賦值語句結合

宣告和賦值語句結合 格式:var identifier [type] = value 這裡的type是可選的,具體的型別參照: 第4章:基本結構和基本資料型別/4.2 Go 程式的基本結構和要素/4.2.8 型別 顯式型別舉例: //整型 var a&nbs

易學筆記-go語言-第4章:基本結構基本資料型別/4.4 變數/4.4.4 函式體內並行初始化

函式體內並行賦值 在 第4章:基本結構和基本資料型別/4.4 變數/4.4.3 函式體內最簡單的變數賦值基礎上,多個變數同時賦值 舉例: 程式碼: a, b, c := 5, 10, "易學筆記"     fmt.Printf("a&n

易學筆記-Go語言-第4章:基本結構基本資料型別/4.5 基本型別/4.5.2 整形

 整形 固定位元組數整形:與作業系統無關 int 和 uint 在 32 位作業系統上,它們均使用 32 位(4 個位元組),在 64 位作業系統上,它們均使用 64 位(8 個位元組)。 uintptr 存放指標 指定位元組

易學筆記-Go語言-第4章:基本結構基本資料型別/4.5 基本型別/4.5.1 bool型別

 bool型別 關鍵字:bool,兩個結果:true 或者 false 何時回產生bool型別 ==:相等性筆記 !=:不相等性筆記 >、>=、<、<=:比較 可以進行的邏輯運算

易學筆記-Go語言-第4章:基本結構基本資料型別/4.4 變數/4.4.7 變數的作用域

變數的作用域 變數的作用域有幾種: 包間變數:也是在函式外宣告的變數,而且第一個字母是大寫,所有本包函式或者包外函式都可見 全域性變數:在函式外宣告的變數,所有函式都可見 區域性變數:在本函式內部都可見 塊變數:僅僅在某個塊中可見,

numpy學習3:物件屬性基本資料型別

一、ndarray物件屬性 ndim 陣列軸(維度)的個數,軸的個數被稱作秩 shape 陣列的維度, 例如一個2排3列的矩陣,它的shape屬性將是(2,3),這個元組的長度顯然是秩,即維度或者ndi

Object、ScannerString、StringBuffer、Integer基本資料型別包裝

Object類 ==號和equals()的區別 ==是一個比較運算子,能比較基本型別和引用資料型別 ==比較的是兩個值是否相等 ==比較引用資料型別,比較的是,地址值是否相同 equals()是object類中的一種方法,這種方法預設比較的是兩個地址值是否相

第4章:基本結構基本資料型別/4.2 Go 程式的基本結構要素/4.2.5 可見性

易學筆記 十年IT經驗個人學習筆記分享: 開發語言:C/C++/JAVA/PYTHON/GO/JSP WEB架構:Servlets/springMVC/springBoot/springClound 容器架構:Docker容器/Docker叢集/Docker與微服務整合/

第4章:基本結構基本資料型別/4.2 Go 程式的基本結構要素/4.2.4 import:匯入包

易學筆記 十年IT經驗個人學習筆記分享: 開發語言:C/C++/JAVA/PYTHON/GO/JSP WEB架構:Servlets/springMVC/springBoot/springClound 容器架構:Docker容器/Docker叢集/Docker與微服務整合/

第4章:基本結構基本資料型別/4.2 Go 程式的基本結構要素/4.2.6 函式

易學筆記 十年IT經驗個人學習筆記分享: 開發語言:C/C++/JAVA/PYTHON/GO/JSP WEB架構:Servlets/springMVC/springBoot/springClound 容器架構:Docker容器/Docker叢集/Docker與微服務整合/

Python 基礎之運算子基本資料型別

1. 運算子 1.1 結果是具體值(數字或字串)的運算子1.1.1 算數運算1.1.2 賦值運算 1.2 結果是布林值的運算子1.2.1 比較運算 1.2.2 邏輯運算 1.2.3 成員運算 2. 基本資料型別入門2.1 字串2.1.1 字串介紹在 python 中,字串可以用單引號、雙引號、三個單引號和

易學筆記-Go語言-第4章:基本結構基本資料型別/4.6 字串概述/4.6.1 字串表示

易學筆記 十年IT經驗個人學習筆記分享: 開發語言:C/C++/JAVA/PYTHON/GO/JSP WEB架構:Servlets/springMVC/springBoot/springClound 容器架構:Docker容器/Docker叢集/Docker與微服務整合/

易學筆記-Go語言-第4章:基本結構基本資料型別/4.5 基本型別/4.5.6 位運算

易學筆記 十年IT經驗個人學習筆記分享: 開發語言:C/C++/JAVA/PYTHON/GO/JSP WEB架構:Servlets/springMVC/springBoot/springClound 容器架構:Docker容器/Docker叢集/Docker與微服務整合/