遛馬少年,一個程式碼寫的很6的程式設計師,專注於技術乾貨分享
最近,在處理線上bug的時候,發現了一個奇怪的現象
業務程式碼大概是這樣的
public static boolean doSth(Integer x, Integer y) {
if (x == y) {
return true;
}
//do other...
return false;
}
當x、y都是較小的值時,比如100、100,正常返回true
當是較大值時,比如500、500,反而返回false
難道100==100,500!=500嗎?
帶著這樣的疑問,我寫了個demo程式一探究竟
public class IntDemo {
public static boolean doSth(Integer a, Integer b) {
if (a == b) {
return true;
}
return false;
}
public static void main(String[] args) {
int a = 100;
int b = 500;
System.out.println(doSth(a, a));
System.out.println(doSth(b, b));
}
}
輸出結果為:
奇怪!底層是怎麼處理的呢?我用javap看了一下上面程式碼的位元組碼指令
public class com.integer.IntDemo {
public com.integer.IntDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static boolean doSth(java.lang.Integer, java.lang.Integer);
Code:
0: aload_0
1: aload_1
2: if_acmpne 7
5: iconst_1
6: ireturn
7: iconst_0
8: ireturn
public static void main(java.lang.String[]);
Code:
0: bipush 100
2: istore_1
3: sipush 500
6: istore_2
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: iload_1
15: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: invokestatic #4 // Method doSth:(Ljava/lang/Integer;Ljava/lang/Integer;)Z
21: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
24: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
27: iload_2
28: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
31: iload_2
32: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
35: invokestatic #4 // Method doSth:(Ljava/lang/Integer;Ljava/lang/Integer;)Z
38: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
41: return
}
可以看到,doSth函式傳入的實參是int型別,函式定義的形參卻是Integer型別
看到第11行位元組碼指令我就懂了,原來是透過Integer.valueOf 來做的一個int的自動裝箱
11: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
所以,問題肯定出在Integer.valueOf裡面,接著,我點開valueOf的原始碼
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
好傢伙,這裡用到了一個快取類:IntegerCache
判斷如果在快取範圍內,直接返回這個快取類持有的引用,否則就new一個Integer物件
再點開這個快取類,low=-128,high=127
這就解釋了為什麼100是true,500是false了
JDK為什麼要設計這樣一個很容易掉進去的坑呢?
其實,在valueOf方法上,官方已經給出了說明:
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
大概意思就是,-128~127 的資料在 int 範圍內是使用最頻繁的,為了減少頻繁建立物件帶來的記憶體消耗,這裡其實是用到了享元模式,以提高空間和時間效能。
既然Integer這樣設計了,其他類會不會也有呢?
接著,我又看了其他資料型別,用快取的還不少,這裡我給各位列一下,防止你們以後踩坑
基本型別 | 包裝型別 | 快取範圍 |
---|---|---|
boolean | Boolean | - |
byte | Byte | -128-127 |
short | Short | -128-127 |
int | Integer | -128-127 |
long | Long | -128-127 |
float | Float | - |
double | Double | - |
小夥伴們在開發過程中,也要注意,避免掉進這個坑裡。
好了,今天的分享就到這裡了,如果你覺得有用,麻煩給兄弟點個小贊,這樣我才更有動力去分享更多技術乾貨~