1. 程式人生 > >java.lang包中的包裝類原始碼分析

java.lang包中的包裝類原始碼分析

八個基本資料型別byte,char,short,int,long,double,float,boolean,對應的包裝類位於java.lang包下面。只有對資料型別更好的瞭解,才能更高效的使用,更得心應手。本文通過整體分析來了解八個包裝類和一個字串類String,分析類設計共性,幾個主要方法,並深入方法的原始碼,探索怎麼實現的。

包裝類設計共性

一、靜態工廠方法 static valueof()

通過閱讀原始碼或者jdk文件就可以知道,大部分包裝類都以靜態常量(static final)或者靜態類(static class)的方式存放著本身的例項。並且每個包裝類都有一個靜態工廠方法valueof(),用來建立物件。 即使他們都有幾個構造器,但我不建議使用,如果你知道valueof()的實現方式的話,你也會有同感。valueof()會判斷要建立的例項是否已經存在,如果存在就直接使用。通過這個方法就可以避免創建出多餘的物件造成記憶體和時間浪費。

例如boolean的包裝類Boolean ,用兩個常量TRUE,FALSE分別存放他的兩個例項。

public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{
    public static final Boolean TRUE = new Boolean(true);

    public static final Boolean FALSE = new Boolean(false);

valueof()的實現則在此的基礎上,使用這個方法,都可以不用再建立任何物件了。(Boolean比較特殊,只有兩個例項)

  public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }

接下來,讓我們看看byte型別的包裝類,Byte,我們都知道 byte的範圍[-128, 127], 所以Byte的所有例項總共有128+127+1種,如果直接用靜態常量來接收,那整個類不就會顯得臃腫,所以,Byte類採用靜態類的方式快取,並使用靜態程式碼塊的方式一次性載入,並只快取一次。

public final class Byte extends Number implements Comparable<Byte> {

    /**
     * A constant holding the minimum value a {@code byte} can
     * have, -2<sup>7</sup>.  最小值
     */
    public static final byte   MIN_VALUE = -128;

    /**
     * A constant holding the maximum value a {@code byte} can
     * have, 2<sup>7</sup>-1.最大值
     */
    public static final byte   MAX_VALUE = 127;
    // 私有靜態類,快取所有Byte例項
    private static class ByteCache {
        private ByteCache(){}

        static final Byte cache[] = new Byte[-(-128) + 127 + 1];
        // 當 i=0, cahe[0] = -128, cahe[1] = -127..... 以此類推
        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Byte((byte)(i - 128));
        }
    }

如何發揮上面這塊程式碼的作用呢? 如果你還在用這樣的方式 new Byte((byte)120); 來建立物件的話,這靜態類就沒有存在的意義了。valueof()致力於建立物件時減少記憶體的消耗。下面Byte類的這個方法實現:這一看出,valueof()不會建立新的物件。完全從快取類中返回。cache[] = {-128,-127-126,,... 0 ,1,2,... ,125,126,127}

 /**
     * @param  b a byte value.
     * @return a {@code Byte} instance representing {@code b}.
     * @since  1.5
     */
    public static Byte valueOf(byte b) {
        final int offset = 128;
        return ByteCache.cache[(int)b + offset];
    }

其他的包裝類 Character ,快取示例的個數也是char的範圍。

 private static class CharacterCache {
        private CharacterCache(){}

        static final Character cache[] = new Character[127 + 1];

        static {
            for (int i = 0; i < cache.length; i++)
                cache[i] = new Character((char)i);
        }
    }

靜態工廠方法,會判斷,如果在例項範圍內,則直接從快取中拿,超過範圍在建立新物件:

 public static Character valueOf(char c) {
        if (c <= 127) { // must cache
            return CharacterCache.cache[(int)c];
        }
        return new Character(c);
    }

此外,另外五個包裝類,也都是類似的設計,都是為了高效的建立物件,減少記憶體消耗。

------2018,-3-19, 補充:

稍微看了一下兩個浮點數 double, float,對應的類。Double,Float 。這兩個的設計和其他的不同,就算使用valueof方法,也是直接建立新物件,而並有事前已經建立好的物件。

    public static Double valueOf(String s) throws NumberFormatException {
        return new Double(parseDouble(s));
    }
  public static Float valueOf(String s) throws NumberFormatException {
        return new Float(parseFloat(s));
    }

二、作為資料型別,基本都重寫了,equals(), hashCode(), 方法 ,另外,因為都實現了Comparable介面,所以都重寫了他的比較方法 compareTo()方法。 但我感覺這裡的這兩個方法實現有點簡單,如果作為集合元素,需要排序時,可能又要重寫。

Byte類:

public static int hashCode(byte value) {
        return (int)value;
    }

    public boolean equals(Object obj) {
        if (obj instanceof Byte) {
            return value == ((Byte)obj).byteValue();
        }
        return false;
    }

Integer類

public boolean equals(Object obj) {
        if (obj instanceof Integer) {    // 一定要做這個判斷 ,object是不是Integer型別的
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
public int compareTo(Integer anotherInteger) {
    return compare(this.value, anotherInteger.value);
}
// x 是當前物件, x.compareTo(y), x < y 返回 -1, 相等 返回0 , x>y 返回1 
public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

這些方法的實現都大同小異,這裡就不一一列舉了。下面我會根據自己看的情況,詳細分析幾個方法的實現過程,這幾個方法的實現,也都會使用到另外幾個類的方法。

幾個重要方法

1、 parseXXX(String s, int radix ) 方法, 

XXX表示基本資料型別(parseInt, parseByte, parseLong 等), 這個方法是用來: 根據radix 進位制,把字串轉化為對應的資料型別。radix既然表示進位制, 就說明他的2的冪數(2,8,10,16,32),通過觀察,Integer類的這個方法比較有代表性,這裡就使用Integet類的原始碼了。另外 兩個浮點數型別的 這個方法的實現和長整數的不同,我後面再會補充。

首先看一下效果:

//使用第二個引數指定的基數,將字串引數解析為有符號的整數。
    public static void testParseInt(){
        int i1 = Integer.parseInt("-111",2);   // -7 
        int i2 = Integer.parseInt("111",4);   // 21
        int i3 = Integer.parseInt("111",8);   // 73
        int i4 = Integer.parseInt("111",10);  // 111
        System.out.println(i1+"--"+i2+"--"+i3+"--"+i4);

    }

原始碼分析:

下面原始碼用到了Character 類的一個方法,digit(char ch,  int radix), ,這個方法是用來檢測字元竄中的字元是否為數字,並且是符合 radix 基數的數字。  如果基數不在 MIN_RADIX <= radix <= MAX_RADIX 範圍之內,或者 ch 的值是一個使用指定基數的無效數字,則返回-1,否則返回本身。

 public static void testDigst(){
        String s = "-5";
        int i = Character.digit(s.charAt(1),2);
        System.out.println(i);      //  結果 -1

        // 基數(用進位制比較好理解)如果字串中的字元是非數字, 則返回-1, 
        // 如果字串的字元不符合進位制的表示式, 則返回-1,  例如: 上面,基數是2, 那麼只能存在0
        // 或者 1 這兩個數字,(完全可以理解為,字串中需要合法的對應的進製表示方式)

        int i2 = Character.digit(s.charAt(1),8);
        System.out.println(i2); // 結果  5   ,  因為八進位制可以用0 到7 之間的數來表示
        
        s= "-a";
        System.out.println(i); // 結果 -1,  因為是非數字
    }

方法的大體思路:

1. 判斷String 是否為空。空就丟擲異常。

2.判斷進位制是否合理(radix), 不合理丟擲異常。

3.對String中的 單個字元進行處理,判斷正負,  如果是非數字, 丟擲異常。

4.主要的計算過程: result = result*radix - digit

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");
        }
        // radix 最小值是2, 不能小於2 Character.MIN_RADIX=2
        if (radix < Character.MIN_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " less than Character.MIN_RADIX");
        }
        // radix 最大值32,  
        if (radix > Character.MAX_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " greater than Character.MAX_RADIX");
        }

        int result = 0;
        boolean negative = false;        // 用來判斷值的正負 , false 是正,  true是負
        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);    // digit方法,
                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;
    }

2、static XXX  XXXValue() , XXX 表示基本資料型別(double, int ,byte, char...),將某個類轉化為對應的資料型別。

3、static xxx decode(String str),    將字串轉化為方法呼叫者類型別資料。

=====後面再補充2,3