微信公眾號:infoQc
如有問題或建議,請公眾號留言
最近更新:2018-08-19
包裝型別
在講解正文之前,我很想問這麼一個問題:"Java為我們提供了8種基本資料型別,為什麼還需要提供各自的包裝型別呢?"。您可能會覺得這個問題問的很奇怪,但是我覺得還是值的思考的。
因為Java是一門物件導向的語言,基本資料型別並不具備物件的性質。而包裝型別則是在基本型別的基礎上,新增了屬性和方法,從而成為了物件。試想,一個int型別怎麼新增到List集合中?
自動拆箱與裝箱
概念:
首先,我們來看一個例子:
1Integer number = 10; //自動裝箱
2int count = number; //自動拆箱
複製程式碼
基本資料型別自動轉化為包裝型別時,即為自動裝箱。
反之,包裝型別自動轉化為基本資料型別時,即為自動拆箱。
看概念您是不是覺得很簡單,但真的這麼"簡單"嗎?不妨我們來看兩道題。
實操:
- 題目一:
1int z = 127;
2Integer a = 127;
3Integer b = Integer.valueOf(127);
4Integer f = new Integer(127);
5Integer g = new Integer(127);
6
7int y = 128;
8Integer c = 128;
9Integer d = Integer.valueOf(128);
10Integer e = new Integer(128);
11Integer h = new Integer(128);
12
13System.out.println("z==a結果:" + (z == a));
14System.out.println("z==b結果:" + (z == b));
15System.out.println("z==f結果:" + (z == f));
16System.out.println("a==b結果:" + (a == b));
17System.out.println("a==f結果:" + (a == f));
18System.out.println("f==g結果:" + (f == g));
19
20System.out.println("y==c結果:" + (y == c));
21System.out.println("y==d結果:" + (y == d));
22System.out.println("y==e結果:" + (y == e));
23System.out.println("c==d結果:" + (c == d));
24System.out.println("c==e結果:" + (c == e));
25System.out.println("e==h結果:" + (e == h));
複製程式碼
- 題目二:
1Integer i = 1;
2Integer j = 2;
3Integer k = 3;
4Long m = 3L;
5Long n = 2L;
6System.out.println("k==i+j結果:" + (k == i + j));
7System.out.println("k.equals(i+j)結果:" + (k.equals(i + j)));
8System.out.println("m==i+j結果:" + (m == i + j));
9System.out.println("m.equals(i+j)結果:" + (m.equals(i + j)));
10System.out.println("m.equals(i+n)結果:" + (m.equals(i + n)));
複製程式碼
思考:
- 什麼情況會觸發自動裝箱操作?
當基本型別賦值給包裝型別引用時,會觸發自動裝箱操作,呼叫包裝型別的valueOf()方法。 - 什麼情況會觸發自動拆箱操作?
當包裝型別參與運算時會觸發自動拆箱操作,呼叫包裝型別對應的***Value()方法,例如Integer類為intValue()。Why?因為運算是基本資料型別要做的事情。
補充:此處利用java提供的反彙編器javap,檢視java編譯器為我們生成的位元組碼。通過它,我們可以對照原始碼和位元組碼,來對上述觀點進行論證。
1Integer number = 10;//自動裝箱
2int number2 = 10;
3Integer number3 = 10;
4System.out.println(number == number2);//自動拆箱
5System.out.println(number + number3);//自動拆箱
6System.out.println(number.equals(number2));//自動裝箱
複製程式碼
由圖中可以看出,執行過程確實如上述程式碼末尾註釋,並且自動裝箱呼叫了valueOf()方法,自動拆箱呼叫了intValue()方法。
- Integer的valueOf()和intValue()又是如何實現的呢?
話不多說,我們來看下Integer這兩個方法的原始碼:
1public int intValue() {
2 return value;
3}
4
5public static Integer valueOf(int i) {
6 if (i >= IntegerCache.low && i <= IntegerCache.high)
7 return IntegerCache.cache[i + (-IntegerCache.low)];
8 return new Integer(i);
9}
複製程式碼
intValue()很簡單,返回Integer物件的value值。
valueOf()判斷當前int值是否在IntegerCache的區間內(-128~127),在則從IntegerCache的cache陣列中獲取指定位置的Integer物件,否則建立一個Integer物件,value值為i。
抱著打破砂鍋問到底的態度,我們繼續看下IntegerCache原始碼:
1private 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}
複製程式碼
IntegerCache作為Integer私有靜態內部類,定義了靜態語句塊,在類載入時會被呼叫。做什麼呢?按照low和high預設為-128~127,依次建立對應數值的Integer物件,並放入cache陣列中,作為快取。通過valueOf()原始碼,我們知道,那裡就是體現它快取價值的地方。
補充:快取裡下限-128不可變,上限預設為127,可以通過java -Djava.lang.Integer.IntegerCache.high=***來動態調整。
實操分析:
有了上述的知識儲備,我們回顧一下之前的兩道題目,是不是容易了很多?
- 題目一解析:
1int z = 127;
2Integer a = 127;
3Integer b = Integer.valueOf(127);
4Integer f = new Integer(127);
5Integer g = new Integer(127);
6
7int y = 128;
8Integer c = 128;
9Integer d = Integer.valueOf(128);
10Integer e = new Integer(128);
11Integer h = new Integer(128);
12
13System.out.println("z==a結果:" + (z == a));//true 和基本資料型別做==,比較的是值,故包裝型別會自動拆箱
14System.out.println("z==b結果:" + (z == b));//true 同理,自動拆箱
15System.out.println("z==f結果:" + (z == f));//true 同理,自動拆箱
16System.out.println("a==b結果:" + (a == b));//true 因127在IntegerCache的-128~127範圍內,故採用快取裡的物件,故二者引用地址一致
17System.out.println("a==f結果:" + (a == f));//false 物件之間==,比較的是引用,因為a指向IntegerCache裡快取的物件,f指向的new出來的物件,故引用地址不同
18System.out.println("f==g結果:" + (f == g));//false f和g都是自己new出來的物件,故引用地址不同
19
20System.out.println("y==c結果:" + (y == c));//true 自動拆箱
21System.out.println("y==d結果:" + (y == d));//true 自動拆箱
22System.out.println("y==e結果:" + (y == e));//true 自動拆箱
23System.out.println("c==d結果:" + (c == d));//false 因128不在IntegerCache的-128~127範圍內,故採用快取裡的物件,故二者引用地址一致
24System.out.println("c==e結果:" + (c == e));//false 引用地址不同
25System.out.println("e==h結果:" + (e == h));//false 引用地址不同
複製程式碼
- 題目二解析:
1Integer i = 1;
2Integer j = 2;
3Integer k = 3;
4Long m = 3L;
5Long n = 2L;
6
7System.out.println("k==i+j結果:" + (k == i + j));//true 首先+操作是數值操作,故i和j會自動拆箱,結果為基本型別int 3。然後k自動拆箱,進行值比較3==3
8System.out.println("k.equals(i+j)結果:" + (k.equals(i + j)));//true 首先+操作是數值操作,故i和j會自動拆箱,結果為基本型別int 3。
9// 然後呼叫equals(Object o)方法,此時int 3自動裝箱,為Integer 3。Integer類的equals方法裡比較的是值,故為true
10
11System.out.println("m==i+j結果:" + (m == i + j));//true 先i和j自動拆箱,再m自動拆箱,比較值。int自動提升為long,值一致為true
12System.out.println("m.equals(i+j)結果:" + (m.equals(i + j)));//false 先i和j自動拆箱,equals(Object o)再自動裝箱,為Integer 3。Long和Integer型別不一致,返回false
13System.out.println("m.equals(i+n)結果:" + (m.equals(i + n)));//true 先i和j自動拆箱,int自動提升為long,故為long 3L。再自動裝箱為Long 3,返回true
複製程式碼
Java快取機制
Java為了在自動裝箱時避免每次去建立包裝型別,採用了快取技術。即在類載入時,初始化一定數量的常用資料物件(即常說的熱點資料),放入快取中,等到使用時直接命中快取,減少資源浪費。
通過檢視原始碼發現,Java提供的8種基本資料型別,除了float和double外,都採用了快取機制。其實原因也很簡單,因為浮點型資料,熱點資料並不好確定,故並未採用。
最後給出各型別快取範圍:
Character: 0~127
Byte,Short,Integer,Long: -128 到 127
Boolean: true,false
大家再遇到類似題目時,就可以正確的做出判斷啦。