Part2 面向對象
第7章 常用基礎類
7.1 包裝類
包裝類有什麼用呢?Java中很多代碼(比如後續文章介紹的集合類)只能操作對象,爲了能操作基本類型,需要使用其對應的包裝類,另外,包裝類提供了很多有用的方法,可以方便對數據的操作。
- 基本類型和包裝類的相互轉換
boolean b1 = false; Boolean bObj = Boolean.valueOf(b1); boolean b2 = bObj.booleanValue(); int i1 = 12345; Integer iObj = Integer.valueOf(i1); int i2 = iObj.intValue();
每種包裝類都有一個靜態方法valueOf()
,接受基本類型,返回引用類型,也都有一個實例方法xxxValue
()返回對應的基本類型。其他如double,float,char等類型也是類似.
-
自動拆箱和裝箱
Integer a = 100; int b = a;
自動裝箱/拆箱是Java編譯器提供的能力,背後,它會替換爲調用對應的valueOf()/xxxValue()
比如說,上面的代碼會被Java編譯器替換爲:
Integer a = Integer.valueOf(100); int b = a.intValue();
-
包裝類構造方法new
Integer a = new Integer(100); Boolean b = new Boolean(true);
那到底應該用靜態的valueOf方法,還是使用new呢?一般建議使用valueOf。new每次都會創建一個新對象
-
重寫Object類方法
所有包裝類都重寫了Object類的如下方法:boolean equals(Object obj) int hashCode() String toString()
-
包裝類和String
1.除了Character外,每個包裝類都有一個靜態的valueOf(String)方法,根據字符串表示返回包裝類對象:Boolean b = Boolean.valueOf("true"); Float f = Float.valueOf("123.45f");
2.有一個靜態的parseXXX(String)方法,根據字符串表示返回基本類型值
boolean b = Boolean.parseBoolean("true"); double d = Double.parseDouble("123.45");
-
共同父類Number
-
不可變性
包裝類都是不可變類,所謂不可變就是,實例對象一旦創建,就沒有辦法修改了。這是通過如下方式強制實現的:
1.所有包裝類都聲明爲了final,不能被繼承
2.內部基本類型值是私有的,且聲明爲了final
3.沒有定義setter方法 -
剖析Integer與二進制算法
reverse
位翻轉
reverseBytes
字節翻轉內部實現原理,理解位運算
-
valueOf的實現
IntegerCache, 共享常用對象:享元模式
7.2 剖析String
Java中處理字符串的類主要是String和StringBuilder
-
基本用法
通過常量定義String變量:String name = "ttee";
通過new創建:
String name = "ttee";
常用函數:
String
中提供了很多函數,熟悉常用函數的用法對我們日常的使用有很大幫助,具體可以參考JDK文檔
-
走進String內部
String內部實現其實是由一個字符數組維護的,String中的絕大多數方法都是在操作這個字符數組。字符數組的定義如下:private final char value[];
如String的
length()
方法的源碼如下:public int length() { return value.length; }
可以看到返回的是value的長度。
chatAt
方法源碼如下:public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }
substring()
方法源碼如下:public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); }
可以看到
substring
其實是計算好要截取的長度,然後調用String的構造函數重新創建了一個字符串。String有兩個構造方法,可以根據char數組創建String:
public String(char value[]) public String(char value[], int offset, int count)
需要說明的是,String會根據參數新創建一個數組,並拷貝內容,而不會直接用參數中的字符數組,其內部實現代碼如下:
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
copyof
方法源碼如下:public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
this.value
即爲String內部維護的實例變量字符數組,可以看到調用copyof
方法會返回一個新創建的char[]
數組,將其賦值給this.value
. -
不可變性
String類有個很重要的特性就是不可變性,即一旦被創建,就不能修改了,通過前面源碼可以看出String類也聲明爲了final,不能被繼承,內部char數組value也是final的,初始化後就不能再變了。
這就可以解釋爲什麼String類裏面很多修改或查找的方法都是新建一個字符串了,如前面的
substring
,大都通過Arrays.copyof()
方法複製新建。 -
常量字符串
Java中的常量字符串就像一個String類型的對象一樣,可以直接調用String的各種方法:System.out.println("ttee".length()); System.out.println("ttee".contains("tt")); System.out.println("ttee".indexOf("ee"));
在內存中,它們被存放在字符串常量池中,每個常量只會保存一份,被所有使用者共享。
我們可以看看下面的代碼例子:String name1 = "ttee"; String name2 = "ttee"; System.out.println(name1==name2);
答案輸出爲true,因爲name1和name2都指向的是字符串常量池中的同一個對象(即所存儲的地址相同),再看下面的代碼:
String name1 = new String("ttee"); String name2 = new String("ttee"); System.out.println(name1==name2);
答案時false,這時會創建兩個String對象,而name1和name2分別指向這兩個對象,只不過這兩個對象內部所存儲的地址都指向相同的char數組。內存佈局如下:
-
hashcode
除了value這個實例變量,String內部還會緩存hash實例變量:private int hash; // Default to 0
hashcode()
方法會計算並返回這個值:public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
如果緩存的hash不爲0,就直接返回了,否則根據字符數組中的內容計算hash
爲什麼要用這個計算方法呢?這個式子中,hash值與每個字符的值有關,每個位置乘以不同的值,hash值與每個字符的位置也有關。使用31大概是因爲兩個原因,一方面可以產生更分散的散列,即不同字符串hash值也一般不同,另一方面計算效率比較高
-
正則表達式
7.3 剖析StringBuilder
-
基本用法
StringBuilder sb = new StringBuilder(); sb.append("tt"); sb.append("ee"); System.out.println(sb.toString()); //out: ttee
-
實現原理
與String類似,StringBuilder類也封裝了一個字符數組,定義如下:char[] value;
與String不同,它不是final的,可以修改。另外,與String不同,字符數組中不一定所有位置都已經被使用,它有一個實例變量,表示數組中已經使用的字符個數,定義如下:
int count;
append實現:
public AbstractStringBuilder append(String str) { if (str == null) str = "null"; int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
append會直接拷貝字符到內部的字符數組中,如果字符數組長度不夠,會進行指數擴展.
參考:《Java編程的邏輯》 第7章 常用基礎類