摘要:性能當字符串是不可變時,字符串常量池才有意義。字符串常量池的出現,可以減少創建相同字面量的字符串,讓不同的引用指向池中同一個字符串,為運行時節約很多的堆內存。
在學習Java的過程中,我們會被告知 String 被設計成不可變的類型。為什么 String 會被 Java 開發者有如此特殊的對待?他們的設計意圖和設計理念到底是什么?因此,我帶著以下三個問題,對
String 進行剖析:
String 真的不可變嗎?
為什么會將 String 設計為不可變?
如何通過技術實現實現 String 不可變 ?
String 真的不可變?String 底層實現:
public final class String implements java.io.Serializable, Comparable, 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 //other codes }
String 的底層實現是依靠 char[] 數組,既然依靠的是基礎類型變量,那么他一定是可變的, String 之所以不可變,是因為 Java 的開發者通過技術實現,隔絕了使用者對 String 的底層數據的操作。但是,我們可以同反射的機制,操作 String 的底層,檢驗其不可變的猜想。
反射的方式操作 String :
//創建字符串"Hello World", 并賦給引用s String s = "Hello World"; System.out.println("s = " + s); // Hello World //獲取String類中的value字段 Field valueFieldOfString = String.class.getDeclaredField("value"); //改變value屬性的訪問權限 valueFieldOfString.setAccessible(true); //獲取s對象上的value屬性的值 char[] value = (char[]) valueFieldOfString.get(s); //改變value所引用的數組中的第5個字符 value[5] = "_"; System.out.println("s = " + s); //Hello_World
通過兩次字符串的輸出,我們可以看到,String 被改變了,但是在代碼里,幾乎不會使用反射的機制去操作 String 字符串,所以,我們會認為 String 類型是不可變的。
為什么會將 String 設計為不可變
安全
引發安全問題,譬如,數據庫的用戶名、密碼都是以字符串的形式傳入來獲得數據庫的連接,或者在socket編程中,主機名和端口都是以字符串的形式傳入。因為字符串是不可變的,所以它的值是不可改變的,否則黑客們可以鉆到空子,改變字符串指向的對象的值,造成安全漏洞
保證線程安全,在并發場景下,多個線程同時讀寫資源時,會引競態條件,由于 String 是不可變的,不會引發線程的問題而保證了線程
HashCode,當 String 被創建出來的時候,hashcode也會隨之被緩存,hashcode的計算與value有關,若 String 可變,那么 hashcode 也會隨之變化,針對于 Map、Set 等容器,他們的鍵值需要保證唯一性和一致性,因此,String 的不可變性使其比其他對象更適合當容器的鍵值。
性能
當字符串是不可變時,字符串常量池才有意義。字符串常量池的出現,可以減少創建相同字面量的字符串,讓不同的引用指向池中同一個字符串,為運行時節約很多的堆內存。若字符串可變,字符串常量池失去意義,基于常量池的String.intern()方法也失效,每次創建新的 String 將在堆內開辟出新的空間,占據更多的內存
實例代碼:
String 的不可變性:
public static String appendStr(String s){ s+="bbb"; return s; } //可變的StringBuilder public static StringBuilder appendSb(StringBuilder sb){ return sb.append("bbb"); } public static void main(String[] args){ //String做參數 String s=new String("aaa"); String ns=Test.appendStr(s); System.out.println("String aaa >>> "+s.toString()); // aaa //StringBuilder做參數 StringBuilder sb=new StringBuilder("aaa"); StringBuilder nsb=Test.appendSb(sb); System.out.println("StringBuilder aaa >>> "+sb.toString()); // aaabbb }String 不可變的技術實現
打開JDK的源碼:
public final class String implements java.io.Serializable, Comparable, 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 //other codes }
String 類由關鍵字 final 修飾,說明該類不可繼承
char value[] 屬性也被 final 所修飾,說明 value 的引用在創建之后,就不能被改變
以上兩點并不能完全實現 String 不可變 ,原因在于:
final int[] value={1,2,3} int[] another={4,5,6}; value=another; // 編譯器報錯,final不可變
value 被 final 修飾,只能保證引用不被改變,但是 value 所指向的堆中的數組,才是真實的數據,只要能夠操作堆中的數組,依舊能改變數據。【解釋:String實際上是可變的】
final int[] value={1,2,3}; value[2]=100; //這時候數組里已經是{1,2,100}
所有的成員屬性均被 private 關鍵字所修飾
為了實現 String 不可變,關鍵在于Java的開發者在設計和開發 String 的過程中,沒有暴露任何的內部成員,與此同時 API 的設計是均沒有操作 value 的值 , 而是采用 new String() 的方式返回新的字符串,保證了 String 的不可變。
JDK String API 源碼:
public static String valueOf(char c) { char data[] = {c}; return new String(data, true); //采用 new String() 的方式返回新的字符串 } 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); //采用 new String() 的方式返回新的字符串 }
整個String設成final禁止繼承,避免被其他人繼承后破壞。所以String是不可變的關鍵都在底層的實現,而不是一個final。考驗的是工程師構造數據類型,封裝數據的功力。
String s = "abcd"; s = "abcdel";
String 不可變性的圖示:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70172.html
摘要:但是通過構造函數的并不是。通過構造函數創建的變量在機制上與其他對象一致,都是在上創建新的對象,然后把引用賦給變量。此外,的方法和等方法實現均是調用了構造函數創建了新的對象,所以他們返回的也都是存在于上的新對象。 String經常在一個語言中或多或少都有些特殊地位。在Java亦不例外。今天先來討論,String是不可變的。 String是引用類型,String變量儲存一個地址,地址指向內...
摘要:什么是不可變對象如果一個對象,在它創建完成后,不能在改變它的狀態,那么這個對象就是不可變的。而在中,是封裝的數組,是在這個數組中的起始位置,是所占的字符的個數。 這是之前在網上看到的一個問題,我就是總結一下。什么是不可變對象:如果一個對象,在它創建完成后,不能在改變它的狀態,那么這個對象就是不可變的。不能改變這個對象的狀態就是:不改變對象內的成員變量,包括基本數據類型的值不能改變,引用...
摘要:原文出自本文總結了程序員常犯的個錯誤。可以看看為什么在中被設計成不可變父類和子類的構造函數以上這段代碼出現編譯錯誤,因為默認的父類構造函數未定義。如果程序員定義構造函數,編譯器將不插入默認的無參數構造函數。 原文出自:http://www.programcreek.com/2014/05/top-10-mistakes-java-developers-make/ 本文總結了J...
摘要:所有變量的類型在編譯時已知在程序運行之前,因此編譯器也可以推導出所有表達式的類型。像變量的類型一樣,這些聲明是重要的文檔,對代碼讀者很有用,并由編譯器進行靜態檢查。對象類型的值對象類型的值是由其類型標記的圓。 大綱 1.編程語言中的數據類型2.靜態與動態數據類型3.類型檢查4.易變性和不變性5.快照圖6.復雜的數據類型:數組和集合7.有用的不可變類型8.空引用9.總結 編程語言中的數據...
摘要:不要疑惑,告訴你答案這個代表正負號的正。雖然一點技術含量沒有,但是你要懂序列也許叫可迭代對象更為合適,但是我喜歡叫序列。 數據結構 可變類型與不可變類型(重頭戲) 基操: 可變類型:[], {} # 可增刪改 查 不可變類型: int float str () # 無法增刪改, 只可查 升操: + 與...
閱讀 1508·2023-04-26 00:25
閱讀 906·2021-09-27 13:36
閱讀 930·2019-08-30 14:14
閱讀 2172·2019-08-29 17:10
閱讀 1006·2019-08-29 15:09
閱讀 1942·2019-08-28 18:21
閱讀 962·2019-08-26 13:27
閱讀 971·2019-08-26 10:58