從 Int 到 Integer 對象,細細品來還是有不少東西

  • 2019 年 10 月 3 日
  • 筆記

int 是 Java 八大原始類型之一,是 Java 語言中為數不多不是對象的東西,Integer 是 int 的包裝類,裏面使用了一個 int 類型的變量來存儲數據,提供了一些整數之間的常用操作,常規性的介紹就這麼一點,程序員不喜歡說,程序員就喜歡源碼,我們還是來看源碼吧

 * @author  Lee Boynton   * @author  Arthur van Hoff   * @author  Josh Bloch   * @author  Joseph D. Darcy   * @since JDK1.0   */  public final class Integer extends Number implements Comparable<Integer> {      /**       * A constant holding the minimum value an {@code int} can       * have, -2<sup>31</sup>.       */      @Native public static final int   MIN_VALUE = 0x80000000;        /**       * A constant holding the maximum value an {@code int} can       * have, 2<sup>31</sup>-1.       */      @Native public static final int   MAX_VALUE = 0x7fffffff;          /**       * The value of the {@code Integer}.       *       * @serial       */      private final int value;        /**       * Constructs a newly allocated {@code Integer} object that       * represents the specified {@code int} value.       *       * @param   value   the value to be represented by the       *                  {@code Integer} object.       */      public Integer(int value) {          this.value = value;      }        /**       * Constructs a newly allocated {@code Integer} object that       * represents the {@code int} value indicated by the       * {@code String} parameter. The string is converted to an       * {@code int} value in exactly the manner used by the       * {@code parseInt} method for radix 10.       *       * @param      s   the {@code String} to be converted to an       *                 {@code Integer}.       * @exception  NumberFormatException  if the {@code String} does not       *               contain a parsable integer.       * @see        java.lang.Integer#parseInt(java.lang.String, int)       */      public Integer(String s) throws NumberFormatException {          this.value = parseInt(s, 10);      }

上面這段源碼是我截取出來的,在 Integer 類中,這些代碼不是連在一起的,把他們放在一起,那是因為我想說明點事情,我們仔細看看這段代碼,Integer 類是被 final ,這說明了什麼?用於存放變量的 value 也被 private final 修飾,這又說明了什麼?看着這些是不是有點熟悉呢? 沒錯,String 對象也是這樣的,這說明 Integer 對象也是不可變的,所以以後如果被問到 Integer 對象是不是不可變對象時,記得回答是喔。為什麼 Integer 對象也會設計成不可變對象呢?其實我也不知道,我沒有從文檔中找到答案,但是在楊曉峰老師的文章中看到過有關說明,楊曉峰老師說 Integer 類設計成不可變跟 getInteger() 方法有關係,getInteger()方法的源碼如下:

public static Integer getInteger(String nm, Integer val) {      String v = null;      try {          v = System.getProperty(nm);      } catch (IllegalArgumentException | NullPointerException e) {      }      if (v != null) {          try {              return Integer.decode(v);          } catch (NumberFormatException e) {          }      }      return val;  }

getInteger() 方法是用來獲取系統屬性的,我們通過屬性來設置服務器的某個服務器的端口,如果 Integer 可變的話,那麼我們就能夠輕易的改變這個屬性的值,這會使得我們的產品存在安全風險。

上面我們我么簡單的聊了一下 Integer 類的實現,聊到 int 與 Integer,自然就少不了自動裝箱和自動拆箱。

1、自動裝箱、拆箱

自動裝箱和拆箱是從 JDK 1.5 開始引進的功能,它是一種語法糖,Java 可以根據上下文,自動的在原始類型和包裝類型中進行轉換,簡單的來說就是 Java 平台保證了不同的寫法通過編譯之後會產生相同的位元組碼,保證了運行時是等價的。自動裝箱和拆箱極大地簡化了相關編程。

自動裝箱:將原始類型轉化為包裝類型的過程

比如將 int 類型轉換成 integer 類型,這就是原始類型到包裝類型的轉變,在編譯的時候,編譯器就會幫我們做自動轉換,這個過程對我們程序員來說是無感知的,例如這段給 Integer 對象賦值的代碼:

Integer x = 3000;

這段代碼經過編譯器之後,會轉換成下面這段代碼:

Integer x = Integer.valueOf(3000);

這就是自動裝箱過程,這個過程你是不知道的,所以它才叫自動裝箱,在自動裝箱的過程中使用到了 valueOf() 方法,來看看 JDK 中這個方法的源碼:

public static Integer valueOf(int i) {      if (i >= IntegerCache.low && i <= IntegerCache.high)          return IntegerCache.cache[i + (-IntegerCache.low)];      return new Integer(i);  }

這個方法里,前面先進行了一個緩存判斷,如果你不知道的話,先忽略掉它,最後返回了 new Integer(i) 對象引用,這個方法就是幫你去調用了 Integer 類的構造器。這就是自動裝箱。

自動拆箱:將包裝類型轉換成原始類型的過程

將 Integer 類型轉換為 Int 類型,這是一個包裝類型轉成成原始類型的過程,在這個過程中就會涉及到自動拆箱。來看看這段代碼(這是一段很操蛋的代碼,實際中應該沒人這樣寫):

Integer mm = 1000;  int mmm = mm;

在編譯的時候,這段代碼會被編譯器編譯成下面這段代碼:

Integer mm = Integer.valueOf(1000);  int mmm = mm.intValue();

主要看int mmm = mm.intValue();這行代碼,這行代碼跟我們寫的不一樣了,使用到了一個 intValue() 方法,來看看 Integer 類中 intValue() 方法的源碼:

/**   * Returns the value of this {@code Integer} as an   * {@code int}.   */  public int intValue() {      return value;  }

這個方法的作用就是把 Integer 對象中用來存儲值的 value 變量返回了,這就是自動拆箱,好了,關於自動裝箱和自動拆箱我們都了解了,還記得自動裝箱過程中涉及到的緩存嗎?接下來我們一起了解一下。

2、Integer 緩存策略

在自動裝箱的 valueOf() 方法中,我們看到了有一個緩存判斷的操作,是的,Integer 類中有緩存池,會將使用頻繁的值緩存起來,以便提高系統的使用性能,在自動裝箱的過程中,會先判斷該值是否存在緩存池中,如果存在直接從緩存池中取出引用返回,如果不存在則調用構造函數構造對象。緩存是自動裝箱操作獨享的,直接通過構造函數構造出來的 Integer 對象即使值在緩存範圍內,也不會使用到緩存池。在 Integer 類中,使用了一個內部類來實現緩存,這個內部類叫做 IntegerCache,IntegerCache 類的源代碼如下:

    /**       * Cache to support the object identity semantics of autoboxing for values between       * -128 and 127 (inclusive) as required by JLS.       *       * The cache is initialized on first usage.  The size of the cache       * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.       * During VM initialization, java.lang.Integer.IntegerCache.high property       * may be set and saved in the private system properties in the       * sun.misc.VM class.       */        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() {}      }

從源碼和 Java 注釋中我們可以看出 IntegerCache 的緩存默認值範圍 -128 ~ 127 。但是我們也可以在啟動時通過 JVM 命令來設置緩存範圍的最大值,只需要在啟動時添加 -XX:AutoBoxCacheMax=
參數就可以了,但是記得這個 size 可不要亂設置,需要全方位考慮,比如你設置成 10 萬,那麼這 10 萬個數都會在啟動剛啟動時就添加到內存中,想想這會佔用你多少內存?這樣做就得不償失了,Java 公司設置成 -128 ~ 127 是有道理的,發現大部分人使用的值都在 -128 ~ 127 之間,這些值佔用的內存比較少,性能上比通過構造函數構造對象要好不少。如何你使用的 Integer 的值在緩存範圍的話,就用 Integer i = value 的形式構建對象,如果你的值不在緩存範圍內,則使用 Integer i = new Integer(value) 的形式構建 Integer 對象,避免自動裝箱的過程。最後我們來看一下 Integer 對象比較常用的方法 parseInt 方法

3、parseInt() 方法

parseInt() 方法的作用是用來將整數型的字符串轉換成整數,parseInt 方法需要和 valueOf 方法區分開來,有不少人會問這兩方法有什麼區別,最後度會返回 int 類型,都能將整數型的字符串轉換成整數型,比如這段代碼

System.out.println(Integer.parseInt("+12"));  System.out.println(Integer.valueOf("+12"));

最後都會輸出 12 ,輸出的結果相同是因為 valueOf 方法使用中會調用 parseInt 方法將整數型字符轉換為整數,並且會在內存中創建一個值為 12 的 Integer 對象,然後返回這個對象引用。而 parseInt 方法只會幫你將整數型字符轉換為整數,不會額外的創建對象。所以它們兩得到相同的結果純屬是巧合。一起瞅瞅 parseInt 源代碼:

public static int parseInt(String s, int radix)                  throws NumberFormatException      {          /*           * WARNING: This method may be invoked early during VM initialization           * before IntegerCache is initialized. Care must be taken to not use           * the valueOf method.           */            if (s == null) {              throw new NumberFormatException("null");          }            if (radix < Character.MIN_RADIX) {              throw new NumberFormatException("radix " + radix +                                              " less than Character.MIN_RADIX");          }            if (radix > Character.MAX_RADIX) {              throw new NumberFormatException("radix " + radix +                                              " greater than Character.MAX_RADIX");          }            int result = 0;          boolean negative = false;          int i = 0, len = s.length();          int limit = -Integer.MAX_VALUE;          int multmin;          int digit;            if (len > 0) {              char firstChar = s.charAt(0);              if (firstChar < '0') { // Possible leading "+" or "-"                  if (firstChar == '-') {                      negative = true;                      limit = Integer.MIN_VALUE;                  } else if (firstChar != '+')                      throw NumberFormatException.forInputString(s);                    if (len == 1) // Cannot have lone "+" or "-"                      throw NumberFormatException.forInputString(s);                  i++;              }              multmin = limit / radix;              while (i < len) {                  // Accumulating negatively avoids surprises near MAX_VALUE                  digit = Character.digit(s.charAt(i++),radix);                  if (digit < 0) {                      throw NumberFormatException.forInputString(s);                  }                  if (result < multmin) {                      throw NumberFormatException.forInputString(s);                  }                  result *= radix;                  if (result < limit + digit) {                      throw NumberFormatException.forInputString(s);                  }                  result -= digit;              }          } else {              throw NumberFormatException.forInputString(s);          }          return negative ? result : -result;      }

在調用 parseInt 方法時,我們可以傳入一個 radix 變量,用來告訴它使用什麼進制來進行轉換,默認使用的是十進制。

文章不足之處,望大家多多指點,共同學習,共同進步

最後

打個小廣告,歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,一起進步吧。
平頭哥的技術博文