String 的不可變性

什么是不可變對(duì)象?

如果一個(gè)對(duì)象,在它創(chuàng)建完成之后,不能再改變它的狀態(tài),那么這個(gè)對(duì)象就是不可變的。不能改變狀態(tài)的意思是,不能改變對(duì)象內(nèi)的成員變量,包括基本數(shù)據(jù)類(lèi)型的值不能改變,引用類(lèi)型的變量不能指向其他的對(duì)象,引用類(lèi)型指向的對(duì)象的狀態(tài)也不能改變

定義一個(gè)字符串

String s = "abcd";

s 中保存了 String 對(duì)象的引用。

使用變量來(lái)賦值變量

String s2 = s;

s2 保存了相同的引用值,因?yàn)樗麄兇硗粋€(gè)對(duì)象。

字符串連接

s = s.concat("ef");

s 中保存的是一個(gè)重新創(chuàng)建出來(lái)的 String 對(duì)象的引用。

String 總結(jié)

一旦一個(gè) String 對(duì)象在內(nèi)存(堆)中被創(chuàng)建出來(lái),它就無(wú)法被修改(因?yàn)?String 類(lèi)的所有成員變量都是 private,并且沒(méi)有提供 public 的 set 方法來(lái)修改這些值。此外成員變量都是 final 的,這就意味著一旦初始化就無(wú)法修改)。特別要注意的是,String 類(lèi)的所有方法都沒(méi)有改變字符串本身的值,都是返回了一個(gè)新的對(duì)象

如果需要一個(gè)可修改的字符串,應(yīng)該使用 StringBuffer 或者 StringBuilder。否則會(huì)有大量時(shí)間浪費(fèi)在垃圾回收上,因?yàn)槊看卧噲D修改都有新的 String 對(duì)象被創(chuàng)建出來(lái)

String 類(lèi)不可變性的好處

  1. 只有當(dāng)字符串是不可變的,字符串池才有可能實(shí)現(xiàn)。字符串池的實(shí)現(xiàn)可以在運(yùn)行時(shí)節(jié)約很多 heap 空間,因?yàn)椴煌淖址兞慷贾赶虺刂械耐粋€(gè)字符串。但如果字符串是可變的,那么將不能實(shí)現(xiàn) String interning(指對(duì)不同的字符串僅僅只保存一個(gè),即不會(huì)保存多個(gè)相同的字符串),因?yàn)檫@樣的話(huà),如果變量改變了它的值,那么其它指向這個(gè)值的變量的值也會(huì)一起改變

  2. 如果字符串是可變的,那么會(huì)引起很?chē)?yán)重的安全問(wèn)題。譬如,數(shù)據(jù)庫(kù)的用戶(hù)名、密碼都是以字符串的形式傳入來(lái)獲得數(shù)據(jù)庫(kù)的連接,或者在 socket 編程中,主機(jī)名和端口都是以字符串的形式傳入。因?yàn)樽址遣豢勺兊?,所以它的值是不可改變的,否則黑客們可以鉆到空子,改變字符串指向的對(duì)象的值,造成安全漏洞

  3. 因?yàn)樽址遣豢勺兊?,所以是多線(xiàn)程安全的,同一個(gè)字符串實(shí)例可以被多個(gè)線(xiàn)程共享。這樣便不用因?yàn)榫€(xiàn)程安全問(wèn)題而使用同步。字符串自己便是線(xiàn)程安全的

  4. 類(lèi)加載器要用到字符串,不可變性提供了安全性,以便正確的類(lèi)被加載。譬如你想加載 java.sql.Connection 類(lèi),而這個(gè)值被改成了 myhacked.Connection,那么會(huì)對(duì)你的數(shù)據(jù)庫(kù)造成不可知的破壞

  5. 因?yàn)樽址遣豢勺兊?,所以在它?chuàng)建的時(shí)候 hashcode 就被緩存了,不需要重新計(jì)算。這就使得字符串很適合作為 Map 中的鍵,字符串的處理速度要快過(guò)其它的鍵對(duì)象。這就是為什么 HashMap 中的鍵往往都使用字符串

String 對(duì)象真的不可變嗎

JDK 6 String 源碼:

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence  
{  
    /** The value is used for character storage. */  
    private final char value[];  
  
    /** The offset is the first index of the storage that is used. */  
    private final int offset;  
  
    /** The count is the number of characters in the String. */  
    private final int count;  
  
    /** Cache the hash code for the string */  
    private int hash; // Default to 0  

JDK 7 String 源碼:

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence {  
    /** The value is used for character storage. */  
    private final char value[];  
  
    /** Cache the hash code for the string */  
    private int hash; // Default to 0 

String 的成員變量是 private final 的,也就是初始化之后不可改變。但是在這幾個(gè)成員中, value 比較特殊,因?yàn)樗且粋€(gè)引用變量,而不是真正的對(duì)象。value 是 final 修飾的,也就是說(shuō) final 不能再指向其他數(shù)組對(duì)象,那么能改變 value 指向的數(shù)組嗎? 比如將數(shù)組中的某個(gè)位置上的字符變?yōu)橄聞澗€(xiàn) “_”。至少在我們自己寫(xiě)的普通代碼中不能夠做到,因?yàn)槲覀兏静荒軌蛟L(fǎng)問(wèn)到這個(gè) value 引用,更不能通過(guò)這個(gè)引用去修改數(shù)組

那么用什么方式可以訪(fǎng)問(wèn)私有成員呢? 沒(méi)錯(cuò),用反射,可以得到 String 對(duì)象中的 value 屬性, 進(jìn)而改變通過(guò)獲得的 value 引用改變數(shù)組的結(jié)構(gòu)

public void testReflection() throws Exception {  
      
    //創(chuàng)建字符串"Hello World", 并賦給引用s  
    String s = "Hello World";   
      
    System.out.println("s = " + s); //Hello World  
      
    //獲取String類(lèi)中的value字段  
    Field valueFieldOfString = String.class.getDeclaredField("value");  
      
    //改變value屬性的訪(fǎng)問(wèn)權(quán)限  
    valueFieldOfString.setAccessible(true);  
      
    //獲取s對(duì)象上的value屬性的值  
    char[] value = (char[]) valueFieldOfString.get(s);  
      
    //改變value所引用的數(shù)組中的第5個(gè)字符  
    value[5] = '_';  
      
    System.out.println("s = " + s);  //Hello_World  
}  

結(jié)果:

s = Hello World
s = Hello_World

在這個(gè)過(guò)程中,s 始終引用的同一個(gè) String 對(duì)象,但是在反射前后,這個(gè) String 對(duì)象發(fā)生了變化,也就是說(shuō),通過(guò)反射是可以修改所謂的“不可變”對(duì)象的。但是一般我們不這么做。這個(gè)反射的實(shí)例還可以說(shuō)明一個(gè)問(wèn)題:如果一個(gè)對(duì)象,它組合的其他對(duì)象的狀態(tài)是可以改變的,那么這個(gè)對(duì)象很可能不是不可變對(duì)象。例如一個(gè) Car 對(duì)象,它組合了一個(gè) Wheel 對(duì)象,雖然這個(gè) Wheel 對(duì)象聲明成了 private final 的,但是這個(gè) Wheel 對(duì)象內(nèi)部的狀態(tài)可以改變, 那么就不能很好的保證 Car 對(duì)象不可變

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 《裕語(yǔ)言》速成開(kāi)發(fā)手冊(cè)3.0 官方用戶(hù)交流:iApp開(kāi)發(fā)交流(1) 239547050iApp開(kāi)發(fā)交流(2) 10...
    葉染柒丶閱讀 28,762評(píng)論 5 20
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,727評(píng)論 18 399
  • 最近看了兩本書(shū), 第一本是Objc.io出的Advanced Swift和王巍大大出的Swifter tips. ...
    MD5Ryan閱讀 1,620評(píng)論 0 1
  • 冬深渾噩噩,堪歲怎重來(lái)?蕭瑟的風(fēng),又吹散了一年的風(fēng)花雪月,而我站在這暮歲的黑夜里,尋找著那些被寒風(fēng)蒼白的歲月,和那...
    苦澀瑪奇朵閱讀 341評(píng)論 0 1

友情鏈接更多精彩內(nèi)容