前言
在 Java 開發中 String (字元串)對象是我們使用最頻繁的對象,也是很重要的對象。正是使用得如此頻繁,String 在實作層面上不斷進行優化,從 Java6 到 Java7,再到 Java9 的新實作 ,都是為了提升 String 對象的性能,而其中不變的是 String 所生俱來的特性:不可變。本文主要聊一聊 String 的不可變,以及為什麼存在的。
什麼是 String 的不可變
首先我們先來看下什麼是不可變對象:一旦對象被建立并初始化後,内部的狀态資料就會保持不變。檢視 JDK 源碼中的 String 類,可以看到類本身被 final 修飾,并且内部的大部分屬性都是 final 修飾的,除了字段 hash 是通過字元串内容計算并緩存起來的。這樣的行為讓 String 類無法被擴充,内部屬性也無法被修改。
接着我們再來用畫圖的形式來說明下 String 的不可變性。
通常我們初始化字元串都是以下形式:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZlBna9QXbm9Fe39DM0YzLcF0YQZUMhtENuRkRJJlaxAVTVRnaxpkeBtEaDdVM3pnZ5clbCp2ZBJ0NuhWe592VHFFTWZkNQhnRUp2TYNXMJ1UTjlGMhlUVEJWa5VVNitGWWdFSVhmahh2LcdGcq9lepJWbt9CXuNmLjlGcx5iepJWbt9CXvwlOzBHd0h2Pw9CX0Vmbu4GZzNmLzN3Lc9CX6MHc0RHaiojIsJye.jpg)
String 類型的引用變量
a
保留了一個字元串對象
string
的引用,就如同下圖所示,箭頭則表示了變量
a
與真正 String 對象的引用關系。
再通過上述代碼,我們将變量
a
指派給變量
b
,變量
b
也存儲了字元串對象
string
的引用,它們指向的是同一個對象。
當我們嘗試對變量
a
重新指派,看下對變量
b
會不會有影響呢
想必小夥伴一看就知道,列印的結果肯定是
string2,string
(圖檔有誤,應該是a=string2)同樣用畫圖的方式展示這兩個變量與字元串對象的引用關系。
将變量
a
重新指派後,儲存了新的引用,而不是直接在原有的字元串對象上進行資料改變,同時變量
b
仍然存的是對象
string
的引用,變量
a
和
b
兩者互相獨立,不影響,這也正是說明了 String 對象的不可變。
在這裡初認 Java 的小夥伴還可能會有些困惑:對一個String對象
a
指派
string
,然後又讓
a
值為
string2
,這個時候a的值變成 了
string2
,
a
的值改變了,為什麼還說 String 對象不可變呢。
其實問題也很簡單,這裡的
a
隻是存儲 String 對象的引用,并不是對象本身,
a
存儲的是指向對象所在記憶體的位址引用罷了,當第二次指派時,
a
引用指向了對象
string2
的記憶體位址,而對象
string2
是重新建立的,之前的
string
對象仍在記憶體中,并且由變量
b
引用着。
除此之外,String 類的傳回 String 對象的方法不會改變自身,都是傳回一個新的 String 對象來實作,比如
concat
,
replace
substring
等等。
為什麼 String 需要不可變
聊完什麼是 String 的不可變後,接下來我們再說說 String 為什麼需要不可變呢,又有什麼好處呢?
字元串常量池的實作
在Java中,我們通常有兩種方式建立字元串對象,一種是通過字元串字面量方式建立,就如上文的代碼,另外一種就是通過 new 方式去建立,如
String c = new String("string 3");
而兩者差別就在于通過字元串字面量的方式建立時,JVM 會現在字元串池中檢查字元串内容是否已經存在,如果存在就會直接傳回對應的引用,而不是再次配置設定記憶體進行建立,如果不存在就會配置設定在記憶體中建立的同時将字元串資料緩存在字元串池中,便于重用。正是是由于字元串的不可變,同樣的字元串内容可以讓 JVM 可以減少額外的記憶體配置設定操作,直接使用在字元串池中字元串對象即可,對性能提升和記憶體節省都大有好處。
關于字元串池,這裡稍微簡單介紹一下:Java 的字元串池屬于 JVM 專門給指定的特殊記憶體區域,用來存儲字元串字面量。在 Java 7 之前,配置設定于 JVM 的方法區内,屬于常量池的一部分;而 Java7 之後字元串池被移至堆記憶體進行管理,這樣的好處就是允許被 JVM 進行垃圾回收操作,将未被引用的字元串所占記憶體即使回收,以此節省記憶體。
Hashcode 緩存
字元串作為基礎的資料結構,大量地應用在一些集合容器之中,尤其是一些散列集合,在散列集合中,存放元素都要根據對象的
hashCode()
方法來确定元素的位置。由于字元串
hashcode
屬性不會變更,保證了唯一性,使得類似 HashMap,HashSet 等容器才能實作相應的緩存功能。由于 String 的不可變,避免重複計算
hashcode
,隻有使用緩存的
hashcode
即可,這樣一來大大提高了在散列集合中使用 String 對象的性能。
線程安全
在多線程中,隻有不變的對象和值是線程安全的,可以在多個線程中共享資料。由于 String 天然的不可變,當一個線程”修改“了字元串的值,隻會産生一個新的字元串對象,不會對其他線程的通路産生副作用,通路的都是同樣的字元串資料,不需要任何同步操作。
安全性
由于字元串無論在任何 Java 系統中都廣泛使用,會用來存儲敏感資訊,如賬号,密碼,網絡路徑,檔案處理等場景裡,保證字元串 String 類的安全性就尤為重要了,如果字元串是可變的,容易被篡改,那我們就無法保證使用字元串進行操作時,它是安全的,很有可能出現 SQL 注入,通路危險檔案等操作。
結語
通過本文,我們介紹 String 是不可變的,可以将它們的引用可以被當作一個普通的變量來使用,無論是在方法間,還是線程間傳遞它們,都不用擔心它指向的實際 String 對象發生改變,并且不可變的特性也在語言層面和程式層面上帶了許多好處,我們也應該在程式設計實踐中多學習效仿,用 James Gosling,Java之父的話說就是,“我會盡可能地使用不可變對象”。
留言交流不過瘾?添加微信:zyc_enjoy
根據指引加入各種主題讨論群
每日一問
今日問題:
王師傅是賣魚的,一斤魚進價45元,現虧本大甩賣,顧客35元買了一公斤,給了王師傅100元假錢,王師傅沒零錢,于是找鄰居換了100元。事後鄰居存錢過程中發現錢是假的,被銀行沒收了,王師傅又賠了鄰居100元,請問王師傅一共虧了多少?
昨日問答:點選>>檢視<<
推薦閱讀
- 這幾款好用超贊的 Google Chrome插件送給你!
- IntelliJ IDEA 2019.2最新解讀
- Spring Cloud與Dubbo的完美融合之手
- 狡猾的 AI 工程師,編個故事騙走 2 億人民币...
- 日均7億交易量,如何設計高可用的MySQL架構?
簽到計劃
活動介紹:自律到極緻-人生才精緻:第13期
活動獎勵:《Spring Cloud微服務:入門、實戰與進階》 x 10
掃描下放二維碼,簽到參與