- String 對象的不可變性
java8中的String只有2個屬性value和hash,相關代碼如下:
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
value是字符串的字符數組,hash是字符串的hash值緩存
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
其中String 類被 final 關鍵字修飾了,而且變量 char 數組也被 final 修飾了。
我們知道類被 final 修飾代表該類不可繼承,而 char[] 被 final+private 修飾,代表了 String 對象不可被更改。Java 實現的這個特性叫作 String 對象的不可變性,即 String 對象一旦創建成功,就不能再對它進行改變。
- Java 這樣做的好處在哪里呢?
第一,保證 String 對象的安全性。假設 String 對象是可變的,那么 String 對象將可能被惡意修改。
第二,保證 hash 屬性值不會頻繁變更,確保了唯一性,使得類似 HashMap 容器才能實現相應的 key-value 緩存功能。
第三,可以實現字符串常量池。在 Java 中,通常有兩種創建字符串對象的方式,一種是通過字符串常量的方式創建,如 String str=“abc”;另一種是字符串變量通過 new 形式的創建,如 String str = new String(“abc”)。
當代碼中使用第一種方式創建字符串對象時,JVM 首先會檢查該對象是否在字符串常量池中,如果在,就返回該對象引用,否則新的字符串將在常量池中被創建。這種方式可以減少同一個值的字符串對象的重復創建,節約內存。
String str = new String(“abc”) 這種方式,首先在編譯類文件時,"abc"常量字符串將會放入到常量結構中,在類加載時,“abc"將會在常量池中創建;其次,在調用 new 時,JVM 命令將會調用 String 的構造函數,同時引用常量池中的"abc” 字符串,在堆內存中創建一個 String 對象;最后,str 將引用 String 對象。
- 平時對String的誤解
平常編程時,對一個 String 對象 str 賦值“hello”,然后又讓 str 值為“world”,這個時候 str 的值變成了“world”。那么 str 值確實改變了,為什么我還說 String 對象不可變呢?
在 Java 中要比較兩個對象是否相等,往往是用 ==,而要判斷兩個對象的值是否相等,則需要用 equals 方法來判斷。
這是因為 str 只是 String 對象的引用,並不是對象本身。對象在內存中是一塊內存地址,str 則是一個指向該內存地址的引用。所以在剛剛我們說的這個例子中,第一次賦值的時候,創建了一個“hello”對象,str 引用指向“hello”地址;第二次賦值的時候,又重新創建了一個對象“world”,str 引用指向了“world”,但“hello”對象依然存在於內存中。也就是說 str 並不是對象,而只是一個對象引用。真正的對象依然還在內存中,沒有被改變。
- String.intern 的原理
首先看下面這段代碼及其結果:
在字符串常量中,默認會將對象放入常量池;在字符串變量中,對象是會創建在堆內存中,同時也會在常量池中創建一個字符串對象,復制到堆內存對象中,並返回堆內存對象引用。
如果調用 intern 方法,會去查看字符串常量池中是否有等於該對象的字符串,如果沒有,就在常量池中新增該對象,並返回該對象引用;如果有,就返回常量池中的字符串引用。堆內存中原有的對象由於沒有引用指向它,將會通過垃圾回收器回收。
使用 intern 方法需要注意的一點是,一定要結合實際場景。因為常量池的實現是類似於一個 HashTable 的實現方式,HashTable 存儲的數據越大,遍歷的時間復雜度就會增加。如果數據過大,會增加整個字符串常量池的負擔。