java 字符串赋值_灵魂拷问:为什么 Java 字符串是不可变的?
生活随笔
收集整理的這篇文章主要介紹了
java 字符串赋值_灵魂拷问:为什么 Java 字符串是不可变的?
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
在逛 programcreek 的時(shí)候,發(fā)現(xiàn)了一些精妙絕倫的主題。比如說(shuō):為什么 Java 字符串是不可變的?像這類(lèi)靈魂拷問(wèn)的主題,非常值得深思。對(duì)于絕大多數(shù)的初級(jí)程序員來(lái)說(shuō),往往停留在“知其然不知其所以然”的層面上——會(huì)用,但要說(shuō)底層的原理,可就只能撓撓頭雙手一攤一張問(wèn)號(hào)臉了。很長(zhǎng)一段時(shí)間內(nèi),我也一直處于這種層面上。導(dǎo)致的局面就是,我在挖一些高深點(diǎn)的技術(shù)方案時(shí),往往束手無(wú)策;在讀一些高深點(diǎn)的技術(shù)文章時(shí),往往理解不了作者在說(shuō)什么。借此機(jī)會(huì),我就和大家一起,對(duì)“為什么 Java 字符串是不可變的”進(jìn)行一次深入地研究。注意了,準(zhǔn)備打怪升級(jí)了!
01. 圖文分析
來(lái)看下面這行代碼。String alita = "阿麗塔";這行代碼在字符串常量池中創(chuàng)建了一個(gè)內(nèi)容為“阿麗塔”的對(duì)象,并將其賦值給了字符串變量 alita(存儲(chǔ)的是字符串對(duì)象"阿麗塔"的引用)。如下圖所示。再來(lái)看下面這行代碼。String wanger = alita;這行代碼將字符串變量 alita 賦值給了字符串變量 wanger。這時(shí)候,wanger 和 alita 存儲(chǔ)的是同一個(gè)字符串對(duì)象的引用。如下圖所示。再來(lái)看下面這行代碼。alita = "戰(zhàn)斗天使".concat(alita);這行代碼將字符串“戰(zhàn)斗天使”拼接在字符串變量 alita 的前面,并重新賦值給 alita。這個(gè)過(guò)程就比之前的復(fù)雜了。我們需要先來(lái)看看?concat()方法做了什么,源碼如下所示。public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true);}可以看得出,"戰(zhàn)斗天使".concat(alita)這行代碼會(huì)先在字符串常量池中創(chuàng)建一個(gè)新的字符串對(duì)象,內(nèi)容為“戰(zhàn)斗天使”,然后?concat()方法會(huì)將其對(duì)應(yīng)的字符數(shù)組和“阿麗塔”對(duì)應(yīng)的字符數(shù)組復(fù)制到一個(gè)新的字符數(shù)組 buf 中,最后,再通過(guò) new 關(guān)鍵字創(chuàng)建了一個(gè)新的字符串對(duì)象,并返回。如下圖所示。從上圖中可以得出結(jié)論,alita 此時(shí)引用的是在堆中新創(chuàng)建的字符串對(duì)象。02. 對(duì)象和對(duì)象引用
可能有些讀者看完上面的圖文分析沒(méi)有理解反而更疑惑了:alita 不是變了嗎?從“阿麗塔”變?yōu)椤皯?zhàn)斗天使阿麗塔”?怎么還說(shuō)字符串是不可變的呢?這里需要給大家解釋一下,什么是對(duì)象,什么是對(duì)象引用。在 Java 中,由于不能直接操作對(duì)象本身,所以就有了對(duì)象引用這個(gè)概念,對(duì)象引用存儲(chǔ)的是對(duì)象在內(nèi)存中的地址。PS:Java 虛擬機(jī)在執(zhí)行程序的過(guò)程中會(huì)把內(nèi)存區(qū)域劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域,如下圖所示。對(duì)象存儲(chǔ)在堆(heap)中,而對(duì)象的引用存儲(chǔ)在棧(stack)中。我們通常所說(shuō)的“字符串是不可變的”是指“字符串對(duì)象是不可變的”。alita 是字符串對(duì)象“阿麗塔”或者“戰(zhàn)斗天使阿麗塔”的引用。這下應(yīng)該明白了吧?03. 源碼分析
我們來(lái)看一下 String 類(lèi)的部分源碼。public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];}可以看得出, String 類(lèi)其實(shí)是通過(guò)操作字符數(shù)組 value 實(shí)現(xiàn)的。而 value 是 private 的,也沒(méi)有提供?serValue()這樣的方法進(jìn)行修改;況且 value 還是 final 的,意味著 value 一旦被初始化,就無(wú)法進(jìn)行改變。另外呢,String 類(lèi)提供的方法,比如說(shuō)?substring():public String substring(int beginIndex) {int subLen = value.length - beginIndex;return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);}toLowerCase():public String toLowerCase(Locale locale) {return new String(result, 0, len + resultOffset);}還有之前提到的?concat(),看似都能改變字符串的內(nèi)容,但其實(shí)都是在方法內(nèi)部使用 new 關(guān)鍵字重新創(chuàng)建的新字符串對(duì)象。04. 為什么要不可變
String 類(lèi)的源碼中還有一個(gè)重要的字段 hash,用來(lái)保存字符串對(duì)象的 hashCode。public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence { /** Cache the hash code for the string */ private int hash; // Default to 0 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; }}因?yàn)樽址遣豢勺兊?#xff0c;所以一旦被創(chuàng)建,它的 hash 值就不會(huì)再改變了。由此字符串非常適合作為 HashMap 的 key 值,這樣可以極大地提高效率。另外呢,不可變對(duì)象天生是線程安全的,因此字符串可以在多個(gè)線程之間共享。舉個(gè)反面的例子,假如字符串是可變的,那么數(shù)據(jù)庫(kù)的用戶名和密碼(字符串形式獲得數(shù)據(jù)庫(kù)連接)將不再安全,一些高手可以隨意篡改,從而導(dǎo)致嚴(yán)重的安全問(wèn)題。05. 最后
總結(jié)一下,字符串一旦在內(nèi)存中被創(chuàng)建,就無(wú)法被更改。String 類(lèi)的所有方法都不會(huì)改變字符串本身,而是返回一個(gè)新的字符串對(duì)象。如果需要一個(gè)可修改的字符序列,建議使用 StringBuffer 或 StringBuilder 類(lèi)代替 String 類(lèi),否則每次創(chuàng)建的字新符串對(duì)象會(huì)導(dǎo)致 Java 虛擬機(jī)花費(fèi)大量的時(shí)間進(jìn)行垃圾回收。參考鏈接:https://www.programcreek.com/2009/02/diagram-to-show-java-strings-immutability總結(jié)
以上是生活随笔為你收集整理的java 字符串赋值_灵魂拷问:为什么 Java 字符串是不可变的?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 御风未来自研 eVTOL M1 飞行器获
- 下一篇: 荣威D7 DMH荣获“中国心