實際開發(fā)中,我們使用Long類型的情景應該是非常多的。可是,你真的完全掌握了Long類型嘛?
測試代碼
首先,我們來看一段測試代碼。該段代碼可以通過測試。
@Test
public void testLong() {
long primaryLong = 127L;
Long long1 = Long.valueOf(primaryLong);
Long long2 = 127L;
Assert.assertTrue(long1 == long2); // 1
long1 = new Long(127L);
Assert.assertFalse(long1 == long2); // 2
primaryLong = 128L;
long1 = Long.valueOf(primaryLong);
long2 = 128L;
Assert.assertFalse(long1 == long2); // 3
long1 = new Long(128L);
Assert.assertFalse(long1 == long2); // 4
}
上述測試代碼中,我們比較了2個Long類型的變量long1和long2之間是否相等,這里我們用了“==”操作符,用來測試2個變量所引用的地址是否相等。
資深碼農應該都創(chuàng)建Long類型有三種方法:new Long()、Long.valueOf()和自動裝箱。上述代碼分別演示了三種方式得到的結果。
- 代碼注析1返回為true。這說明在使用值127L時,不管使用Long.valueOf賦值創(chuàng)建Long變量還是使用自動裝箱將long轉換為Long類型,2個變量都指向同一位置。
- 代碼注析2返回為false。這說明使用new Long(127L)創(chuàng)建出的Long變量,跟剛才使用自動裝箱創(chuàng)建的Long變量不再指向同一位置。
- 代碼注析3、4都返回為false。這說明在使用值128L創(chuàng)建Long變量后,每次都指向不同的位置,不管是new Long、Long.valueOf還是自動裝箱。
這是怎么回事?特別是對于Long.valueOf還是自動裝箱這兩種方法,居然在值為127L和128L的時候結果不一樣?
要想徹底理解上述代碼為什么是這樣的結果,還得從源代碼入手。
分析
我們打開java.lang.Long的源代碼。其中,關鍵性代碼如下。
public final class Long extends Number implements Comparable<Long> {
private final long value; // 1
private static class LongCache { // 2
private LongCache(){} // 2.1
static final Long cache[] = new Long[-(-128) + 127 + 1]; // 2.2
static { // 2.3
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
@Deprecated(since="9")
public Long(long value) { // 3
this.value = value;
}
@Deprecated(since="9")
public Long(String s) throws NumberFormatException { // 4
this.value = parseLong(s, 10);
}
public static Long valueOf(long l) { // 5
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
}
-
Long類繼承自Number基類,Number類為一個抽象類,代表數。下圖展示了Integer、Long、Byte、Short、Float和Double都是繼承自Number。
image
該類只有一些抽象方法XXXValue。如下圖所示。
image 注析1代表Long內部實際上存儲的還是一個變量value,用來存儲long類型原始數據。
注析3、4則是Long的2個構造方法,2個方法都是對value進行賦值,不做其他處理。
-
我們來看注析2和5:注析2是一個內部靜態(tài)類LongCache,從名字上看,該類應該是Long類型的緩存:
- 注析2.1表示LongCache有一個private構造方法,用來阻止對該類的實例化。private構造方法通常用在工具類或者單實例類中,例如常見的單例模式就需要private構造方法。
- 注析2.2表示該類有一個Long類型的數組cache,并且?guī)tatic final修飾。該數組的大小為
-(-128) + 127 + 1=256。
帶有final的數組一經初始化就不能再對其重新賦值,但是還是可以通過對下標的引用修改cache數組各元素的值。 - 注析2.3表示對cache進行初始化。下標0到256分別初始化為-128到128之間的值。
接下來看注析5,當參數l為-128到127之間的值時,直接返回的是LongCache內部cache數組中對應下標中的值。這里很巧妙的設置offset為128,從而將最小值-128的值的下標設置為(int)l + offset=0。對不屬于[-128, 127]范圍之類的值,則調用new Long返回。
同時,留意構造方法處的
@Deprecated(since="9")注解,表示從JDK9開始,構造方法已經不被推薦使用。JDK文檔中,推薦使用靜態(tài)工廠方法Long.valueOf來構造Long對象,能夠獲得更好的時間和空間表現。
根據語法標準,自動封裝實際上調用的就是Long.valueOf,而不是構造方法。
綜上分析,對于[-128, 127]的值,不管使用Long.valueOf還是自動裝箱,最終都是讀取LongCache.cache的同一下標的值,故而“==”為真。其他情況,2各不同的變量“==”比較為false。
拓展
- 對于Short類,源代碼中ShortCache類和valueOf方法的實現和Long類型一模一樣。這里就不展開了。
- 對于Byte類,只能容納-128到127之間的值。valueOf方法能夠通過ShortCache.cache返回所有的值。
- 對于Float和Double類,沒有所謂的FloatCache和DoubleCache,所以每次構建的都是新對象。
- 為避免上述代碼中出現的“==”比較出現的結果差異性。建議對象之間的之間比較,我們使用equals或者將包裝類拆裝成原始類型再使用“==”比較,上述測試代碼中,可以使用long1.longValue()或者+long1將Long轉換為long再使用“==”比較
注意,+long1一定是確保了+long1不會返回null。否則會報NullPointerException。
這里給一個小小的測試,看看大家理解的如何?
Long long1 = new Long(100L);
Long long2 = 100L;
Long long3 = 100L;
Long long4 = 128L;
Long long5 = 128L;
System.out.println(long1 == long2);
System.out.println(+long1 == long2);
System.out.println(long2 == long3);
System.out.println(+long2 == long3);
System.out.println(long4 == long5);
System.out.println(+long4 == long5);
以上代碼你認為輸出什么?
<details>
<summary>答案</summary>
<pre>
<code>
false
true
true
true
false
true
</code>
</pre>
</details>
覺得好?關注微信公眾號:技術之禪(微信號"zen_of_java")獲取更多干貨和資源!
公眾號回復"java","spring","javascript","python","english"等獲取福利。