Java-Integer好大一坑,一不小心就掉進去了

遛馬少年發表於2023-02-09

遛馬少年,一個程式碼寫的很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));
   }
}

輸出結果為:

image.png

奇怪!底層是怎麼處理的呢?我用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 -

小夥伴們在開發過程中,也要注意,避免掉進這個坑裡。

好了,今天的分享就到這裡了,如果你覺得有用,麻煩給兄弟點個小贊,這樣我才更有動力去分享更多技術乾貨~

相關文章