為什么Java中String是不可變的,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。
創(chuàng)新互聯(lián)公司主營(yíng)崇左網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都APP應(yīng)用開發(fā),崇左h5小程序定制開發(fā)搭建,崇左網(wǎng)站營(yíng)銷推廣歡迎崇左等地區(qū)企業(yè)咨詢
String 是 Java 語言非?;A(chǔ)和重要的類,提供了構(gòu)造和管理字符串的各種基本邏輯。它是典型的 Immutable 類,被聲明成為 final class,所有屬性也都是 final 的(hashCode方法除外)。也由于它的不可變性,類似拼接、裁剪字符串等動(dòng)作,都會(huì)產(chǎn)生新的 String 對(duì)象。
不可變對(duì)象是指一旦創(chuàng)建完成就不能再被修改的對(duì)象。也就說一旦一個(gè)對(duì)象被賦值了,這個(gè)對(duì)象的引用或者內(nèi)部狀態(tài)都不能被修改,對(duì)象所暴露的public方法不允許你修改
使用不可變對(duì)象有四個(gè)優(yōu)點(diǎn):緩存,安全,同步,性能。使用不可變對(duì)象可以省去很多不必要的麻煩,而且很對(duì)場(chǎng)景下只能使用不可變對(duì)象(例如緩存結(jié)果)。對(duì)于可變對(duì)象傳參或者賦值的時(shí)候,必須要copy這個(gè)可變參數(shù)的值,因?yàn)槲覀儾恢肋@個(gè)可變對(duì)象的值何時(shí)會(huì)被修改。但如果使用的是final類型,就只需要copy這個(gè)變量的引用。因?yàn)镴ava確保了final類型的值不會(huì)被修改。
String使用一個(gè)聲明為final的字符數(shù)組value來存儲(chǔ)值,value一旦賦值后所指向的地址就不能再被修改。雖然數(shù)組的值可以修改,但是String類本身并沒有提供能夠修改value數(shù)組值的方法。
/** The value is used for character storage. */
private final char value[];
補(bǔ)充一下:當(dāng)使用final修飾基本類型變量時(shí),不能對(duì)基本類型變量重新賦值,因此基本類型變量不能被改變;但對(duì)于引用類型的變量,JDK保證的是變量指向的地址不會(huì)被改變(即指向的始終是同一個(gè)對(duì)象),至于地址的值(對(duì)象的值)是可以改變的。
在前文中指出使用String這種不可變對(duì)象便于緩存,而jdk就為String提供了字符串常量池。字符串是最常使用的數(shù)據(jù)結(jié)構(gòu),緩存String并重復(fù)使用可以極大的節(jié)省Heap空間,因?yàn)橄嗤腟tring變量指向了常量池中相同地址的值。例如以下代碼只在字符串常量池中創(chuàng)建一個(gè)String對(duì)象
String string1 = "abcd";
String string2 = "abcd";
System.out.println(string1 == string2);
//代碼輸出結(jié)果為true
如圖所示,如果String是可變的,那修改string1變量所指向的地址的內(nèi)容,就會(huì)導(dǎo)致string2的內(nèi)容也被修改了
String在Java應(yīng)用中被大量用于存儲(chǔ)敏感信息(用戶名,密碼,URL等),也被JVM Class Loader用于加載類信息,因此十分有必要保證String的安全
void criticalMethod(String userName) {
// 1.安全檢查
if (!isAlphaNumeric(userName)) {
throw new SecurityException();
}
// 2.更新DB
connection.executeUpdate("UPDATE Customers SET Status = 'Active' " +
" WHERE UserName = '" + userName + "'");
}
如上代碼片段,方法傳入一個(gè)userName的變量,第一步先檢查改變量是否都是字母數(shù)字,然后執(zhí)行sql 假設(shè)String是可變的,且方法已經(jīng)執(zhí)行過代碼1還未執(zhí)行代碼2的時(shí)候,這時(shí)候上游調(diào)用方還持有參數(shù)userName的引用,如果調(diào)用方此時(shí)修改了userName的值,那就相當(dāng)于混過了代碼1出的安全檢查。那么SQL注入的風(fēng)險(xiǎn)就會(huì)增加了
“不可變”保證在并發(fā)編程模型中,不同Thread不能修改String的值,如果某個(gè)Thread修改了一個(gè)String值,這個(gè)值就會(huì)被緩存到StringPool中,也就是說對(duì)于String的引用是安全的。
JDK提供了很多hash操作的數(shù)據(jù)結(jié)構(gòu):HashMap,HashTable,HashSet等,當(dāng)在這些類上操作的時(shí)候,hashCode()方法就會(huì)被頻繁的調(diào)用?!安豢勺儭北WC了String對(duì)象的值不會(huì)被修改,所以hash值也就不會(huì)被修改。對(duì)于不變的值就可以緩存起來以便之后調(diào)用hasCode()方法的時(shí)候就可以直接取出緩存中的值 JDK源碼中String代碼片段如下:
/** Cache the hash code for the string */
private int hash; // Default to 0
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
*
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
*
* using {@code int} arithmetic, where {@code s[i]} is the
* ith character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
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;
}
可以看到String對(duì)象有一個(gè)屬性hash用于緩存hasCode()第一次計(jì)算的結(jié)果,之后調(diào)用hashCode()方法就直接從緩存中取出來
關(guān)于為什么Java中String是不可變的問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。