前言:本博文將涉及的Java的自動裝箱和自動拆箱,可以參考 這篇文章 和 官方教程 ,這裡不再贅述。
首先,先看一個小程式:
public class Main { public static void main(String[] args){ Integer i1 = new Integer(1); Integer i2 = new Integer(1); System.out.println(i1 == i2); Integer i3 = 1; Integer i4 = 1; System.out.println(i3 == i4); Integer i5 = 200; Integer i6 = 200; System.out.println(i5 == i6); } }
上面的程式會依次輸出false 、true、false。
第一個輸出語句應該比較好理解,就是建立了不同的物件。但是第二跟第三個輸出語句估計很多人就很難理解了。
要解釋這個問題,需要從快取說起。
快取
快取是軟體設計模式中一個非常有用的模式,快取的實現方式有很多,不同方式可能存在效能上的差別。下面給出一個用陣列實現的例項:
(1)快取類Cache_test
/* * <p> * 該物件使用陣列實現了快取,也就是, * 每一次使用valueOf()建立新物件時,系統將會確認快取中是否已經存在相應的物件(即data相等)。 * 假如存在,則直接返回快取已存在的物件; * 假如不存在,則建立一個新物件,儲存到快取中,並返回新建立的物件。 * </p> * * @author Harvin. * @version 1.0 */ public class Cache_test { //需要儲存的資料 private final String data; public Cache_test(String data){ this.data = data; } public String get_data(){ return this.data; } @Override //直接判斷是否是指向同一個物件 public boolean equals(Object obj){ if (this == obj) { return true; } return false; } //定義快取的大小 private final static int MAX_SIZE = 10; //使用陣列來儲存快取 private static Cache_test[] cache = new Cache_test[MAX_SIZE]; //定義當前快取儲存的位置 private static int pos = 0; /* 判斷是否已經快取了包含該data物件的Cache_test物件, * 如果存在,則直接返回; * 如果不存在,則直接建立後再將其返回 */ public static Cache_test valueOf(String data){ for (int i = 0; i < MAX_SIZE; i++) { if (cache[i] != null && cache[i].get_data().equals(data)) { return cache[i]; } } if(MAX_SIZE == pos){ cache[0] = new Cache_test(data); pos = 1; }else{ cache[pos] = new Cache_test(data); } return cache[pos++]; } }
(2)測試類Main
public class Main { public static void main(String[] args){ Cache_test ct1 = new Cache_test("test1"); Cache_test ct2 = new Cache_test("test1"); //由於這裡都是直接建立,所以下面會輸出false; System.out.println(ct1 == ct2); Cache_test ct3 = Cache_test.valueOf("test2"); Cache_test ct4 = Cache_test.valueOf("test2"); //由於這裡使用的是valueOf()函式,將會使用到快取。所以下面輸出true. System.out.println(ct3 == ct4); } }
上面的例子中,實現原理為:使用一個陣列來快取該類的物件,陣列的長度為MAX_SIZE。每一次呼叫valueOf來建立物件時,快取池將會先去查詢快取池中是否已經存在該物件,如果存在,則直接返回該物件,所以當輸入兩個相同data時,返回回來的物件是同一個,所以上面 ct3 和 ct4 為同一個物件。當快取陣列不存在該物件時,快取池將根據傳入的引數建立一個新的物件,再將其儲存到快取陣列中。另外,在這裡快取池使用的是“先進先出”的原則。
PS:上面例項中,用於Cache_test的建構函式為共有,所以,允許建立不儲存到快取池中的物件,假如要強制使用快取池,則可以將建構函式宣告為private。
瞭解了快取原理後,我們來看看實際JDK中使用了快取的類。
包裝類 Integer 的快取
類似於我們上面提到的快取原理,Integer類如果使用new建構函式來建立物件,則每次都將返回全新的物件;假如採用了valueOf方法來建立物件,則會快取該建立的物件。讓我們來看看原始碼:
private static class IntegerCache {//內部類,注意它的屬性都是定義為static final static final inthigh; //快取上界 static final Integer cache[];//cache快取是一個存放Integer型別的陣列 static {//靜態語句塊 final int low = -128;//快取下界,值不可變 // high value may beconfigured by property int h = 127;// h值,可以通過設定jdk的AutoBoxCacheMax引數調整(參見(3)) if (integerCacheHighPropValue !=null) { // Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized // 通過解碼integerCacheHighPropValue,而得到一個候選的上界值 int i = Long.decode(integerCacheHighPropValue).intValue(); // 取較大的作為上界,但又不能大於Integer的邊界MAX_VALUE i = Math.max(i, 127);//上界最小為127 // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); } high = h; //上界確定,此時high預設一般是127 // 建立快取塊,注意快取陣列大小 cache =new Integer[(high - low) + 1]; int j = low; for(int k = 0; k <cache.length; k++) cache[k] =new Integer(j++);// -128到high值逐一分配到快取陣列 } private IntegerCache() {}//構造方法,不需要構造什麼
簡單來說,就是使用了一個內部類IntegerCache 來管理快取cache[]。但使用valueOf()方法時,系統將會判斷是否存在於快取池中。然而,請注意,這裡有所不同的是,Integer類在載入時,就已經預先將一部分物件(即從-128到127)建立好了,也就是說每一次呼叫valueOf方法時,假如傳入的值在-127到128之間,則Integer類直接返回已經建立好的物件,假如傳入的引數值在此區間之外,則Integer類會建立一個全新的物件。
再看小程式
現在,讓我們重新回來一開始的小程式。
(1)程式中 i1 和 i2 利用其建構函式進行構造,所以,兩者是兩個不同的物件,因此返回false。
(2)通過使用javap 檢視位元組碼,可知 i3 和 i4 、i5 和 i6 的自動裝箱事實上是呼叫了valueOf方法。i3 和 i4 的值在-128到127之間,所以直接使用快取池的物件,而 i5 和 i6 超出該區間,所以建立的是新物件。
由此便可以得知所以輸出結果了。
後記
通過資料查詢和原始碼的檢視,可以知道,除了Integer類外,還有Byte、Short、Long、Character也使用了快取,而Flot、Double沒有使用快取。
相關資料
《Integer中用靜態內部類所作的快取》
《Java中的裝箱與拆箱》
《Java 自動裝箱和拆箱》