(一)關於如何查看Java中String類的源碼?
String類源碼在JDK文件中的src壓縮包下,src-java-lang-String.java。
(二)String類源碼分析
2.1 定義
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
可以看到String類是被final修飾的,這就意味着String是不可以被繼承,這樣String類中的方法是沒有機會被覆
蓋的,簡而言之,String類是不可變類。與此同時,該類實現了三個接口Serializable, Comparable,CharSequence。
2.2 屬性
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
/** The value is used for character storage. */
private final char value[];
這是一個字符數組,並且是final類型,他用於存儲字符串內容,從fianl這個關鍵字中我們可以看出,String的內容
一旦被初始化了是不能被更改的。 雖然有這樣的例子: String s = “a”; s = “b” 但是,這並不是對s的修改,而是重新指
向了新的字符串,從這裏我們也能知道,String其實就是用char[]實現的。
2.3 爲什麼字符串要被設計成不可變類
a、如果字符串可變的話,當兩個引用指向指向同一個字符串時,對其中一個做修改就會影響另外一個。
b、在HashMap中,字符串的不可變能保證其hashcode永遠保持一致,這樣就可以避免一些不必要的麻煩。這也就意味着
每次在使用一個字符串的hashcode的時候不用重新計算一次,這樣更加高效。
private int hash;//this is used to cache hash code.
以上代碼中hash
變量中就保存了一個String對象的hashcode,因爲String類不可變,所以一旦對象被創建,該hash值也
無法改變。所以,每次想要使用該對象的hashcode的時候,直接返回即可。
c、安全性方面:String被廣泛的使用在其他Java類中充當參數。比如網絡連接、打開文件等操作。如果字符串可變,那麼
類似操作可能導致安全問題。因爲某個方法在調用連接操作的時候,他認爲會連接到某臺機器,但是實際上並沒有(其他
引用同一String對象的值修改會導致該連接中的字符串內容被修改)。可變的字符串也可能導致反射的安全問題,因爲他的
參數也是字符串。
2.4 Java中的equals()和hashcode()的關係
所有Java類的父類——java.lang.Object
中定義了兩個重要的方法:
public boolean equals(Object obj)
public int hashCode()
下面是一段摘取自網絡的代碼:
import java.util.HashMap;
public class Apple {
private String color;
public Apple(String color) {
this.color = color;
}
public boolean equals(Object obj) {
if(obj==null) return false;
if (!(obj instanceof Apple))
return false;
if (obj == this)
return true;
return this.color.equals(((Apple) obj).color);
}
public static void main(String[] args) {
Apple a1 = new Apple("green");
Apple a2 = new Apple("red");
//hashMap stores apple type and its quantity
HashMap<Apple, Integer> m = new HashMap<Apple, Integer>();
m.put(a1, 10);
m.put(a2, 20);
System.out.println(m.get(new Apple("green")));
}
}
上面的代碼執行過程中,先是創建個兩個Apple,一個green apple和一個red apple,然後將這來兩個apple存儲在map中,存儲之後再試圖通過map的
get方法獲取到其中green apple的實例。讀者可以試着執行以上代碼,數據結果爲null。也就是說剛剛通過put方法放到map中的
green apple並沒有通過get方法獲取到。你可能懷疑是不是green apple並沒有被成功的保存到map中,但是,通過debug工具可以
看到,它已經被保存成功了。
造成以上問題的原因其實比較簡單,是因爲代碼中並沒有重寫hashcode
方法。hashcode
和equals
的約定關係如下:
1、如果兩個對象相等,那麼他們一定有相同的哈希值(hash code)。
2、如果兩個對象的哈希值相等,那麼這兩個對象有可能相等也有可能不相等。(需要再通過equals來判斷)
如果你瞭解Map的工作原理,那麼你一定知道,它是通過把key值進行hash來定位對象的,這樣可以提供比線性存儲更好的性能。實際上,Map的底層
數據結構就是一個數組的數組(準確的說其實是一個鏈表+數組)。第一個數組的索引值是key的哈希碼。通過這個索引可以定位到第二個數組,第二個
數組通過使用equals方法進行線性搜索的方式來查找對象。
其實,一個哈希碼可以映射到一個桶(bucket)中,hashcode
的作用就是先確定對象是屬於哪個桶的。如果多個對象有相同的哈希值,那麼他們可以放
在同一個桶中。如果有不同的哈希值,則需要放在不同的桶中。至於同一個桶中的各個對象之前如何區分就需要使用equals
方法了。
hashcode方法的默認實現會爲每個對象返回一個不同的int類型的值。所以,上面的代碼中,第二個apple被創建出來時他將具有不同的哈希值。可以通
過重寫hashCode方法來解決。
public int hashCode(){
return this.color.hashCode();
}
所以,在判斷兩個對象是否相等時,不要只使用equals方法判斷。還要考慮其哈希碼是否相等。尤其是和hashMap等與hash相關的數據結構一起使用時。
(未完待續。。。。。。)