Java中變量類型可分爲兩類:基本類型和引用類型。基本類型有8種,分別是short,int,long,byte,char,float,double,boolean,同時也有8種引用類型做爲其包裝類,例如Integer,Double等。本文要討論的就是這些基本類型和其包裝類。java
下面涉及到源碼的部分也僅僅列出部分有表明性的源碼,不會有大段的代碼。我使用的JDK版本是JDK1.8_144程序員
Integer是基本類型int的包裝類型。下圖是其繼承體系:算法
Integer繼承了Number類,實現了Comparable便可,擁有可比較的能力,還間接的實現了Serializable接口,便是可序列化的。實際其餘幾個數字相關的包裝類型都是這麼一個繼承體系,因此若是下文中沒有特殊說明,就表示繼承體系和Integer同樣。數組
先來看看valueOf()方法,在Integer類裏有三個重載的valueOf()方法,但最終都會調用public static Integer valueOf(int i)。下面是public static Integer valueOf(int i)的源碼:緩存
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
複製代碼
能夠看到,該方法接受一個基本類型int參數,並返回一個Integer對象,須要注意的是那個if判斷,這裏會判斷這個i是否在[IntegerCache.low,IntegerCache.high]區間裏,若是在就直接返回緩存值,那這個low和hight的值是多少呢?咱們到IntegerCache類裏看看:測試
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() {}
}
複製代碼
這是Integer的一個內部靜態類,從代碼中,不難看出low的值是-128,high的值默認是127,其中high值能夠經過java.lang.Integer.IntegerCache.high屬性來設置,這個類的邏輯代碼幾乎都在static塊裏,也就是說在類加載的時候會被執行了,執行的結果是什麼呢?關注這幾行代碼:spa
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
複製代碼
將區間在[low,hight]的值都構形成Integer對象,並保存在cache數組裏。這時候再回頭看看valueOf()方法,發現若是參數i的值在[low,hight]區間裏,那麼就會返回cache裏對應的Integer對象,話句話說,若是參數在這個區間範圍裏,那麼調用該方法的時候就不須要建立對象了,直接使用緩存裏的對象,減小了建立對象的開銷。指針
那爲何要搞這麼複雜呢?由於Java5提供了自動裝箱和自動拆箱的語法糖,而這個方法其實就是爲自動裝箱服務的(從註釋能夠看到,這個方法從java5纔開始有的,由此可推斷應該和自動裝箱拆箱機制有關),Integer是一個很是經常使用的類,有了這個緩存機制,就不須要每次在自動裝箱或者手動調用的時候建立對象了,大大提升了對象複用率,也提供了運行效率。[-128,127]是一個比較保守的範圍,用戶能夠經過修改java.lang.Integer.IntegerCache.high來修改high值,但最好不要太大,由於畢竟Intger對象也是要佔用很多內存的,若是上界設置的太大,而大的值又不常用,那麼這個緩存就有些得不償失了。code
再來看看另外一個方法intValue(),這個方法就很是簡單了:orm
public int intValue() {
return value;
}
複製代碼
value是Integer類的私有字段,類型是int,就是表明了實際的值,該方法實際上就是自動拆箱的時候調用的方法。
其餘方法就沒什麼可說的了,大多數是一些功能性的方法(例如max,min,toBinaryString)和一些常量(例如MAX_VALUE,MIN_VALUE),感興趣的朋友能夠自行查看,算法邏輯也都不難。
其餘幾個就很少說了,在理解了Integer的源碼以後,其餘的都不難看懂。
自動裝箱和自動拆箱是Java5提供的語法糖,當咱們須要在基本類型和包裝類之間進行比較、賦值等操做的時候,編譯器會幫咱們自動進行類型轉換,裝箱就是將基本類型轉換成包裝類型,拆箱就是與之相反的一個過程。例如,咱們有下面這樣的代碼:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
int a = 1, b = 2, c = 3;
list.add(a);
list.add(b);
list.add(c);
}
複製代碼
編譯後的.class文件內容以下(作了轉碼的,實際上.class文件應該是字節碼,但爲了方便查看,就將其轉成了java代碼的形式):
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
byte var2 = 1;
byte var3 = 2;
byte var4 = 3;
var1.add(Integer.valueOf(var2));
var1.add(Integer.valueOf(var3));
var1.add(Integer.valueOf(var4));
}
複製代碼
這裏就發生了裝箱操做,編譯器幫咱們調用了Integer.valueOf()方法將int類型轉換了Integer類型以適應List容器(容器的類型參數不能是基本類型)。
拆箱也相似,只是方向和裝箱相反而已,通常發生在將包裝類型的值賦值給基本類型時候,例如:
Integer a = 1;
int b = a; //自動拆箱
複製代碼
若是沒有自動拆箱,上面的代碼確定是沒法編譯經過,由於類型不匹配,又沒有進行強轉。
關於自動裝箱和拆箱更多的內容,網上有不少資料(很是很是多),建議有興趣的朋友能夠到網上搜索搜索。
首先,咱們先明確一個基本原則:若是某個場景中不能使用基本類型和包裝類型的其中一種,那麼就只能選擇另一種。例如,容器的泛型類型參數不能是基本類型,那就不要有什麼猶豫了,直接使用包裝類型吧。
當兩種類型均可用的時候,建議使用基本類型,主要緣由有以下3個:
包裝類型是一個類,要使用某個值就必需要建立對象,即便有緩存的狀況下能夠節省建立對象的時間開銷,但空間開銷仍然存在,一個對象包含了對象頭,實例數據和對象填充,但實際上咱們僅僅須要實例數據而已,對於Integer來講,咱們僅僅須要value字段的值而已,也就是說使用基本類型int只佔用了4個字節的空間,而使用對象須要幾倍於基本類型的空間(你們能夠計算一下)。
接着第1條,對象是有可能爲null的,這樣在使用的時候有時候不得不對其進行空值判斷,少數幾個還好,當業務邏輯複雜的時候(或者程序員寫代碼寫上頭的時候),就很是容易遺漏,一旦遺漏就頗有可能發生空指針異常,若是沒有嚴密的測試,還真不必定能測試出來。
包裝類在進行比較操做的時候有時候會讓人迷惑,咱們舉個例子,假設有以下代碼:
public static void main(String[] args) {
Integer d = 220;
Integer e = 220;
int f = 220;
System.out.println(d == e);
System.out.println(e == f);
}
複製代碼
若是沒有遇到過這樣的問題,可能下意識的說出執行的結果是true和true,但實際上,輸出的結果是false,true。爲何?對於第一個比較,d和e都是Integer對象,直接用==比較的實際上引用的值,而不是實際的值,在將220賦值給她們的時候實際上發生過自動裝箱,即調用過valueOf()方法,而220超出了默認的緩存區間,那麼就會建立新的Interge對象,這兩個a和b的引用確定就不相等,因此會輸出false。第二個比較中,f是基本類型,在這裏狀況下,會發生自動拆箱,==兩邊的變量實際上基本類型int,直接比較時沒有問題的,因此輸出結果會是true。
總之,若是能用基本類型,最好仍是使用基本類型,而不是其包裝類,若是實在沒法使用基本類型,例如容器的泛型類型參數,才使用包裝類。
本文介紹了Integre包裝類,對其源碼進行了簡單分析,之因此沒有對其餘基本類型的保證類進行分析是由於他們的實現很是類似,重複介紹就沒有意思了。還簡單討論了一下自動裝箱和自動拆箱,這個特性方便了程序員,提升了開發效率,但若是使用不當可能會形成使人迷惑的問題,算是有利有弊吧。對於在基本類型和包裝類型之間的選擇,我我的傾向於優先使用基本類型,緣由也在文中有比較詳細的解釋。