Java源碼 : Int包裝類 -- Integer

1. Integer類的基本信息

NOTE : 以JDK 1.8 爲準,補充部分屬性、方法在 JDK-1.6 / JDK-1.7中的變化說明;

  • 繼承自: Number類
  • 實現了:Serializable和Comparable接口

Integer類的結構和方法可以劃分爲以下區域:

  1. 類定義、屬性:7個屬性,都很好理解;
  2. 核心方法:構造Integer的方法和一些核心方法;
  3. int -> String方法:將int數字轉換各種進制的字符串 ;
  4. String -> int方法:將各種進制的字符串解析爲int數字 ;
  5. unsigned -> signed:java的int是有符號的,需要提供處理未符號的其他進制的方法;
  6. signed -> unsigned:將無符號的其他進制字符串轉換爲有符號的int數字;

2. 前提基礎

有符號和無符號

在計算機表中,正負數的表示可以使用有符號和無符號兩種表示,
- Java使用的是有符號的十進制表示方法,最高位爲符號位,所以表示範圍爲 - 2^31 到 2^31;
- 對於無符號表示並不是說無法表示負數,而是最高位也用來表示數本身,負數用“補碼”形式表示;

無符號表示可以翻閱很多資料:http://blog.csdn.net/sunweixiang1002/article/details/53048080

自動裝箱和拆卸

自動裝箱和拆卸是Java的語法糖,之所以需要分析Integer的源碼即因爲我們在日常編程中有很多自動裝箱和拆卸機制,這些會給我們帶來一些“坑”;

16進制語法糖

Java編程允許我們用Oxxxxx這樣的形式定義一個16進制數據,如 int x = Ox11 ,實際上x就是17,Java編譯時會將Ox11翻譯爲17,所以Integer的進制轉換方法,不需要提供類似 : toDeciamlInt(int i); 這樣的方法。

3. 核心方法 源碼分析

只看使用頻率最高的方法,其他的如 int -> String/ String -> Int 、有符號到無符號、進制轉換,這些不常用,有需要再看下源碼即可。

3.1 屬性

7個屬性,1個添加自1.8,主要是提升一些效率,避免運行時計算;

// 在Number中實現了Serializable接口
// 所以子類自動實現Serializable接口
public final class Integer extends Number implements Comparable<Integer> {

    //最小值,表示 -2^31
    //使用二進制分析就是最高位外後面31個0
   @Native public static final int   MIN_VALUE = 0x80000000;

    //最大值,表示2^31 -1
    @Native public static final int   MAX_VALUE = 0x7fffffff;

    //類型緩存
    public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

    // JDK 1.5添加,表示int 數字的位長度爲32位;
    @Native public static final int SIZE = 32;

    // JDK 1.8添加,計算好需要的字節長度,1字節8位,其實就是 4
    public static final int BYTES = SIZE / Byte.SIZE;

    //真正的值,final的不可以被動態改變
    private final int value;

     @Native 
    private static final long serialVersionUID = 1360826667806852920L;
}

3.2 常用的核心方法

之所以使用Integer對象,就是int作爲基本類型不具有對象方法,使用Integer對象頻率最高的即以下方法。
另外需要使用Integer對象的場景一般如下兩種:

  1. 需要使用一個特殊的值(null)表示無法計算,程序需要自己處理,比如從CacheUtil提供incry方法,如果key不存在則不計算,那麼可以返回Integer,如果key不存在返回null,業務判斷null則從其他數據源加載值來計算;
  2. 作爲Map的Key,這是最常用的場景,Map要求必須有hashCode和equals。
    // 這裏定義了一個內部靜態類,用來緩存[-128,127]之間的Integer對象;
    // 這個緩存是爲了JSL的自動裝箱機制提供的,可以通過VM參數:-XX:AutoBoxCacheMax=<size>來調整這個緩存範圍
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // cache的最大值可以通過屬性和vm參數設置
            int h = 127;
            // 去VM這個類查詢參數,看是否配置了AutoBoxCacheMax的值;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);

                    // 檢測配置的最大值不能超過 MAX_VALUE - 129;
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                }
            }

            //下面就是提前初始化,new出這些Integer對象出來
            high = h;

              // 緩存的最小值無法修改是:-128,
            // 最大值不能超過 MAX_VALUE - 129 , 
            // 如果high超過了,那麼數組分配的時候會拋出:NegativeArraySizeException
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

              //斷言機制,JLS7 要求範圍必須大於[-128, 127]
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

    //JDK 1.5提供的方法,優先使用valueOf方法替代new方法;
    public static Integer valueOf(int i) {
          // 先判斷是不是在緩存範圍內,內部類直接用屬性了。
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        //超出緩存範圍,直接new一個    
        return new Integer(i);
    }

    public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }

    public static Integer valueOf(String s) throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));
    }

    // 一般情況下不推薦使用
    public Integer(int value) {
        this.value = value;
    }

    public Integer(String s) throws NumberFormatException {
        this.value = parseInt(s, 10);
    }

    // 縮小位數,會拋出高位內容
    public byte byteValue() {
        return (byte)value;
    }

    // 縮小位數,會拋棄高位內容
    public short shortValue() {
        return (short)value;
    }

     //非常常用的一個方法
    public int intValue() {
        return value;
    }

    //擴寬位數
    public long longValue() {
        return (long)value;
    }

     //擴寬位數
    public float floatValue() {
        return (float)value;
    }

    //擴寬位數
    public double doubleValue() {
        return (double)value;
    }

     //默認轉換爲10進制
    public String toString() {
        return toString(value);
    }


  /**
     * 仔細看看這個方法,非常簡單,就是返回value值作爲hashCode.
     * Integer.valueOf(1); -> 1;
     * */
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    //JDK 1.8 抽象出來
    public static int hashCode(int value) {
        return value;
    }

    /**
     * equals很熟悉,原理還是比較value是否== 但是這裏先判斷了是不是Integer的;
     * 在代碼中 Integer x = 1; x.equasl(1L); 這是能編譯過去的,但是返回的是false;
     * 這會引發一系列問題,尤其是作爲Map的Key,要注意,此類場景不常用,但是容易採坑;
     */
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }


    //JDK 1.2 提供的比較接口,用來排序
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }

    // 1.7 新增
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

核心方法不多,而且大部分都很簡單,核心方法中有一段IntegerCache,這是第重點,是爲了支持Java的autoboxing機制,提升自動裝箱和拆箱的效率;

3.2.1 自動裝箱和拆箱

Java的自動裝箱和拆卸其實是一種語法糖,比如如下代碼:

    Integer x = 1;
    // 編譯後實際上的寫法是:
    Integer x = Integer.valuof(1);

3.2.2 == 比較的問題

對象的 == 判斷是判斷的引用,那麼Integer的 == 也不例外,但是自動裝箱可能破壞我們的認知,比如如下代碼:

    Integer x = 1;
    System.out.println(x == 1); // case 1-0 : true 自動拆箱
    System.out.println(1 == x); // case 1-1 : true 自動拆箱
    Integer y = 1000;
    System.out.println(y == 1000); // case 2 : true 自動拆箱
    System.out.println(y == new Integer(1000)); // case : false 沒有拆箱

根據源碼可以分析出:

  • X == 1 這個不管是拆箱還是裝箱,== 都是true, 那麼實際上是被裝箱了還是拆箱了呢?
  • y == new Integer(10000) ,因爲y被自動裝箱了,而且IntegerCache還沒有緩存,所以肯定是否false;
  • y == 1000 這個就比較疑惑了,如果對1000自動裝箱,那麼就是false,如果對y自動拆箱那麼肯定就是true;

javap -c的反編譯內容如下:

Code:
        ## 這三個指令就是Integer x = Integer.valueOf(1);
       0: iconst_1
       1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: astore_1

       ## 這幾條指令就是System.out.println(x == 1);
       ## 很明顯x == 1使用的是 x.intValue == 1 
       ## 也就是自動拆箱了
       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: aload_1
       9: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      12: iconst_1
      13: if_icmpne     20
      16: iconst_1
      17: goto          21
      20: iconst_0
      21: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V

      ## 這幾條指令就是System.out.println(1 == x);
      ## 這裏也證明了是自動拆箱了
      24: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      27: iconst_1
      28: aload_1
      29: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      32: if_icmpne     39
      35: iconst_1
      36: goto          40
      39: iconst_0
      40: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V

      ## 這裏1000沒有被預編譯到常量池
      ## 先自動裝箱 Integer y = Integer.valueOf(1000);
      43: sipush        1000
      46: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      49: astore_2

      ## 這裏自動拆箱了,System.out.println(y.intValue == 1000);
      50: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      53: aload_2
      54: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      57: sipush        1000
      60: if_icmpne     67
      63: iconst_1
      64: goto          68
      67: iconst_0
      68: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V

      ## 這裏編譯時沒有拆箱用比較的是integer的==
      71: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      74: aload_2
      75: new           #6                  // class java/lang/Integer
      78: dup
      79: sipush        1000
      82: invokespecial #7                  // Method java/lang/Integer."<init>":(I)V
      85: if_acmpne     92
      88: iconst_1
      89: goto          93
      92: iconst_0
      93: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      96: return

NOTE : Java的編譯器在Integer和int比較的時候使用的是拆箱機制,Integer的比較要使用equals不要使用 == ,雖然這在一定範圍內可以被編譯器優化爲自動拆箱,並且因爲緩存的原因有時是正確的,但是實際上很危險。

3.2.3 valueOf的使用

Integer在構造的時候推薦使用valueOf而不是new Integer,valueOf會首先判斷是否需要緩存,也會造成如下的不同:

    Integer x = 120;
    Integer y = 120;
    Integer x2 = 150;
    Integer y2 = 150;
    System.out.println(x == y);(case 1 : true)
    System.out.println(x == new Integer(120)); (case 2 : false)
    System.out.println(x == Integer.valueOf(120)); (case 2 : true)
    System.out.println(x2 == y2) ; // case 4 : false
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章