您好,登錄后才能下訂單哦!
為什么Java中String是不可變的,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
String 是 Java 語言非常基礎和重要的類,提供了構造和管理字符串的各種基本邏輯。它是典型的 Immutable 類,被聲明成為 final class,所有屬性也都是 final 的(hashCode方法除外)。也由于它的不可變性,類似拼接、裁剪字符串等動作,都會產生新的 String 對象。
不可變對象是指一旦創建完成就不能再被修改的對象。也就說一旦一個對象被賦值了,這個對象的引用或者內部狀態都不能被修改,對象所暴露的public方法不允許你修改
使用不可變對象有四個優點:緩存,安全,同步,性能。使用不可變對象可以省去很多不必要的麻煩,而且很對場景下只能使用不可變對象(例如緩存結果)。對于可變對象傳參或者賦值的時候,必須要copy這個可變參數的值,因為我們不知道這個可變對象的值何時會被修改。但如果使用的是final類型,就只需要copy這個變量的引用。因為Java確保了final類型的值不會被修改。
String使用一個聲明為final的字符數組value來存儲值,value一旦賦值后所指向的地址就不能再被修改。雖然數組的值可以修改,但是String類本身并沒有提供能夠修改value數組值的方法。
/** The value is used for character storage. */
private final char value[];
補充一下:當使用final修飾基本類型變量時,不能對基本類型變量重新賦值,因此基本類型變量不能被改變;但對于引用類型的變量,JDK保證的是變量指向的地址不會被改變(即指向的始終是同一個對象),至于地址的值(對象的值)是可以改變的。
在前文中指出使用String這種不可變對象便于緩存,而jdk就為String提供了字符串常量池。字符串是最常使用的數據結構,緩存String并重復使用可以極大的節省Heap空間,因為相同的String變量指向了常量池中相同地址的值。例如以下代碼只在字符串常量池中創建一個String對象
String string1 = "abcd";
String string2 = "abcd";
System.out.println(string1 == string2);
//代碼輸出結果為true
如圖所示,如果String是可變的,那修改string1變量所指向的地址的內容,就會導致string2的內容也被修改了
String在Java應用中被大量用于存儲敏感信息(用戶名,密碼,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 + "'");
}
如上代碼片段,方法傳入一個userName的變量,第一步先檢查改變量是否都是字母數字,然后執行sql 假設String是可變的,且方法已經執行過代碼1還未執行代碼2的時候,這時候上游調用方還持有參數userName的引用,如果調用方此時修改了userName的值,那就相當于混過了代碼1出的安全檢查。那么SQL注入的風險就會增加了
“不可變”保證在并發編程模型中,不同Thread不能修改String的值,如果某個Thread修改了一個String值,這個值就會被緩存到StringPool中,也就是說對于String的引用是安全的。
JDK提供了很多hash操作的數據結構:HashMap,HashTable,HashSet等,當在這些類上操作的時候,hashCode()方法就會被頻繁的調用。“不可變”保證了String對象的值不會被修改,所以hash值也就不會被修改。對于不變的值就可以緩存起來以便之后調用hasCode()方法的時候就可以直接取出緩存中的值 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
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th 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對象有一個屬性hash用于緩存hasCode()第一次計算的結果,之后調用hashCode()方法就直接從緩存中取出來
關于為什么Java中String是不可變的問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。