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