1. 程式人生 > >為什麼String被設計為不可變?是否真的不可變?

為什麼String被設計為不可變?是否真的不可變?

1 物件不可變定義 

不可變物件是指物件的狀態在被初始化以後,在整個物件的生命週期內,不可改變。 

2 如何不可變 

通常情況下,在java中通過以下步驟實現不可變

  1. 對於屬性不提供設值方法
  2. 所有的屬性定義為private final
  3. 類宣告為final不允許繼承
  4. Return deep cloned objects with copied content for all mutable fields in class

注意:不用final關鍵字也可以實現物件不可變,使用final只是顯示的宣告,提示開發者和編譯器為不可變。

3 Java中典型的不可變類為String類 

為什麼String被設計為不可變?

  • 安全首要原因是安全,不僅僅體現在你的應用中,而且在JDK中,Java的類裝載機制通過傳遞的引數(通常是類名)載入類,這些類名在類路徑下,想象一下,假設String是可變的,一些人通過自定義類裝載機制分分鐘黑掉應用。如果沒有了安全,Java不會走到今天
  • 效能 string不可變的設計出於效能考慮,當然背後的原理是string pool,當然string pool不可能使string類不可變,不可變的string更好的提高效能。
  • 執行緒安全 當多執行緒訪問時,不可變物件是執行緒安全的,不需要什麼高深的邏輯解釋,如果物件不可變,執行緒也不能改變它。 

4 為什麼String是不可變

區分物件和物件的引用

對於Java初學者, 對於String是不可變物件總是存有疑惑。看下面程式碼:  
		String s = "ABCabc";
		System.out.println("s = " + s);
		
		s = "123456";
		System.out.println("s = " + s);

  輸出結果:

s = ABCabc s = 123456 首先建立一個String物件s,然後讓s的值為“ABCabc”, 然後又讓s的值為“123456”。 從列印結果可以看出,s的值確實改變了。那麼怎麼還說String物件是不可變的呢? 其實這裡存在一個誤區: s只是一個String物件的引用,並不是物件本身。物件在記憶體中是一塊記憶體區,成員變數越多,這塊記憶體區佔的空間越大。引用只是一個4位元組的資料,裡面存放了它所指向的物件的地址,通過這個地址可以訪問物件。 也就是說,s只是一個引用,它指向了一個具體的物件,當s=“123456”; 這句程式碼執行過之後,又建立了一個新的物件“123456”, 而引用s重新指向了這個心的物件,原來的物件“ABCabc”還在記憶體中存在,並沒有改變。記憶體結構如下圖所示:

 

為什麼String物件是不可變的?

要理解String的不可變性,首先看一下String類中都有哪些成員變數。 在JDK1.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

  在JDK1.7中,String類做了一些改動,主要是改變了substring方法執行時的行為,這和本文的主題不相關。JDK1.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

  由以上的程式碼可以看出, 在Java中String類其實就是對字元陣列的封裝。JDK6中, value是String封裝的陣列,offset是String在這個value陣列中的起始位置,count是String所佔的字元的個數。在JDK7中,只有一個value變數,也就是value中的所有字元都是屬於String這個物件的。這個改變不影響本文的討論。 除此之外還有一個hash成員變數,是該String物件的雜湊值的快取,這個成員變數也和本文的討論無關。在Java中,陣列也是物件。 所以value也只是一個引用,它指向一個真正的陣列物件。其實執行了String s = “ABCabc”; 這句程式碼之後,真正的記憶體佈局應該是這樣的:

value,offset和count這三個變數都是private的,並且沒有提供setValue, setOffset和setCount等公共方法來修改這些值,所以在String類的外部無法修改String。也就是說一旦初始化就不能修改, 並且在String類的外部不能訪問這三個成員。此外,value,offset和count這三個變數都是final的, 也就是說在String類內部,一旦這三個值初始化了, 也不能被改變。所以可以認為String物件是不可變的了。 那麼在String中,明明存在一些方法,呼叫他們可以得到改變後的值。這些方法包括substring, replace, replaceAll, toLowerCase等。例如如下程式碼:
		
		String a = "ABCabc";
		System.out.println("a = " + a);
		a = a.replace('A', 'a');
		System.out.println("a = " + a);
		

  輸出結果:

a = ABCabc a = aBCabc 那麼a的值看似改變了,其實也是同樣的誤區。再次說明, a只是一個引用, 不是真正的字串物件,在呼叫a.replace('A', 'a')時, 方法內部建立了一個新的String物件,並把這個心的物件重新賦給了引用a。
		String ss = "123456";
		
		System.out.println("ss = " + ss);
		
		ss.replace('1', '0');
		
		System.out.println("ss = " + ss);
列印結果: ss = 123456
ss = 123456

String物件真的不可變嗎?

從上文可知String的成員變數是private final 的,也就是初始化之後不可改變。那麼在這幾個成員中, value比較特殊,因為他是一個引用變數,而不是真正的物件。value是final修飾的,也就是說final不能再指向其他陣列物件,那麼我能改變value指向的陣列嗎? 比如將陣列中的某個位置上的字元變為下劃線“_”。 至少在我們自己寫的普通程式碼中不能夠做到,因為我們根本不能夠訪問到這個value引用,更不能通過這個引用去修改陣列。 那麼用什麼方式可以訪問私有成員呢? 沒錯,用反射, 可以反射出String物件中的value屬性, 進而改變通過獲得的value引用改變陣列的結構。下面是例項程式碼:
	public static void testReflection() throws Exception {
		
		//建立字串"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
	}
列印結果為: s = Hello World
s = Hello_World 在這個過程中,s始終引用的同一個String物件,但是再反射前後,這個String物件發生了變化, 也就是說,通過反射是可以修改所謂的“不可變”物件的。但是一般我們不這麼做。這個反射的例項還可以說明一個問題:如果一個物件,他組合的其他物件的狀態是可以改變的,那麼這個物件很可能不是不可變物件。例如一個Car物件,它組合了一個Wheel物件,雖然這個Wheel物件宣告成了private final 的,但是這個Wheel物件內部的狀態可以改變, 那麼就不能很好的保證Car物件不可變。 連結:https://blog.csdn.net/zhangjg_blog/article/details/18319521

相關推薦

為什麼String設計可變?是否真的可變

1 物件不可變定義  不可變物件是指物件的狀態在被初始化以後,在整個物件的生命週期內,不可改變。  2 如何不可變  通常情況下,在java中通過以下步驟實現不可變 對於屬性不提供設值方法 所有的屬性定義為private final 類宣告為final不允許繼承 Return deep cloned obj

String為何設計可變

String類為什麼是final的 1. 首先我們先要理解final的用途: final表示最終的意思 什麼是不可變類? 2. String為什麼要用f

String設計可變繼承的原因

String是所有語言中最常用的一個類。我們知道在Java中,String是不可變的、final的。Java在執行時也儲存了一個字串池(String pool),這使得String成為了一個特別的類。 主要是為了 “ 效率 ” 和 “ 安全性 ” 的緣故。若 String 

java裏String類為何設計final

使用 hashtable 方向 memory 思維 per 垃圾收集器 其他 tro   前些天面試遇到一個非常難的關於String的問題,“String為何被設計為不可變的”?類似的問題也有“String為何被設計為final?”個人認為還是前面一種問法更準確,設計成fi

應用程序啟動器 “sublime_text.desktop“ 還沒有標記 信任。如果您知道這個文件的來源,那麽啟動它可能會安全。解決sublime在ubuntu中支持中文輸入問題。

Go fix sudo ons 啟動 show -- ica 完美 1.下載 git clone https://github.com/lyfeyaj/sublime-text-imfix.git 2.進行一些處理 cd ~/sublime-text-imfix sud

Date類為什麼設計可變的,而是像String一樣?

首先,不得不承認,這確實是類庫設計的一個錯誤,所以“為什麼”進行了這個錯誤設計並沒有意義。但沒有事物一誕生就是完美的,我們的Java只是反應的慢了一點,再慢了一點。 更何況,Date類等日期/時間API又不是隻有這一個問題。 Java8之前,日期/時

為什麼要把Java字串設計可變

String是Java中一個不可變的類,所以它一旦被例項化就無法被修改。不可變類一旦被建立就不可以被修改。本文將從記憶體、同步和資料結構相關知識簡單說明一下將String設計為不可變類的好處。 (1)字串池: 字串池是方法區中一部分特殊儲存。當一個字串被建立的時候,首先會去字串池

為什麼Java中的字串定義可變

字串,想必大家最熟悉不過了,通常我們在程式碼中有幾種方式可以建立字串,比如: String s = “Hollis”; 這時,其實會在堆記憶體中建立一個字串物件,其中儲存了一個字元陣列,該陣列中儲存了字串的內容。 上面的箭頭可以理解為“儲存他的引用”。 當我們

為什麼String設計可變

1. 字串常量池的需要 字串常量池(String pool, String intern pool, String保留池) 是Java堆記憶體中一個特殊的儲存區域, 當建立一個String物件時,假如此字串值已經存在於常量池中,則不會建立一個新的物件,而是引用已經存在的物件

使用@import導入實現了ImportBeanDefinitionRegistrar接口的類,註冊bean

sage lur watch ref java throw 根據 lib spa 今天在調試公司spring項目的時候發現了這樣一個問題,由於我們的項目使用的是springboot就以springboot為例,代碼如下: @Import({DataSourceRegi

SQLServer 遠程服務器存在,未指定有效的發布服務器,或您無權查看可用的發布服務器

name role 服務器角色 nbsp bsp 狀態 tails 可用 img 原文:SQLServer 遠程服務器不存在,未被指定為有效的發布服務器,或您無權查看可用的發布服務器 創建了事務發布,在初始化時出現錯誤,查看相關代理信息如下: 日誌讀取器代理

談談聯想柳傳誌1票投決支持美國高通而誤解愛國

談談聯想柳傳誌1票投決支持美國高通而被題目:談談聯想柳傳誌1票投決支持美國高通而被誤解為不愛國 我覺得在我國的實體經濟,真的是非常難以發展,靠炒房來錢快的人,不知是真的怎麽想的?而作為以電腦為主業的聯想公司,應該叫做電子信息化產品公司,也算是中國的實體經濟的一部分,在電子信息產品這塊,聯想真的做的非常的棒,是

iis 7上發布mvc報錯:403.14-Forbidden Web 服務器配置列出此目錄的內容

OS asp 影響 directory spn 內容 server 解決 並且 iis 7上發布mvc報錯:403.14-Forbidden Web 服務器被配置為不列出此目錄的內容 提示裏面的解決方法是: 如果不希望啟用目錄瀏覽,請確保配置了默認文檔並且該文件存在

HTTP 錯誤 403.14 - Forbidden Web 服務器配置列出此目錄的內容

分享圖片 3.1 技術 requests http 錯誤 con 解決方法 true ima 一、錯誤 HTTP 錯誤 403.14 - Forbidden Web 服務器被配置為不列出此目錄的內容,如圖所示: 二、解決方法: 1、在web.config中加

C++函式中那些可以宣告虛擬函式的函式

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

這大概就是這世界設計得如此“確定”的原因之一罷

哈維爾說:“一個不能從自身汲取力量和不能在自身內部發現其生命意義的人,將依賴於周圍環境,本質上是一種狂熱盲從的態度。這種人是不幸的:他總是慷慨激昂同時又始終失望,他只是廉價的樂觀主義者。真正的堅定不移表現在,有力量保持清醒嚴肅的精神和理性,健康的自制和對世界獨特的觀點。”  學會隨

設計一個遞迴演算法刪除帶頭節點單鏈表L中所有值x的節點

#include "stdafx.h" #include<stdio.h> #include<malloc.h> #include<stdlib.h> typed

windows環境,teamviewer13檢測商務用途,能連線,重新安裝顯示用於“個人用途”的選項,還需要刪除“regedit”登錄檔下teamviewer資訊

剛學習使用TeamViewer的使用者可能不知道當我們的軟體使用過期了該怎麼辦,尤其是個人免費版為什麼會過期以及怎麼解決呢?今天小編就來教大家解決此類問題的方法吧! 圖:TeamViewer被檢測為商用 當然,以下操作步驟和解決方法僅限於個人被誤檢

聯想V480”虛擬機器配置64位客戶機作業系統,但是64位操作可用,已該虛擬機器禁用長模式“的解決辦法

為了學習研究,需要安裝一個64位windows,但是又不重灌安裝作業系統,只好想辦法在32位機器上安裝。在網上查了查資料,發現CPU支援VT技術的就能支援vmware中安裝64位虛擬機器。 以下是操作步驟: 1)到網上下載一個securable.exe,測試以下機器是

dede後臺提示你的使用者名稱存在 管理員spider

今天,開啟網站後臺,輸入,帳號密碼進行登入。dede提示你的使用者名稱不存在,我就納悶了,怎麼使用者名稱就不存在了,我又沒改。我以為輸錯了,又輸入了一遍。結果還是同樣的提示你的使用者名稱不存在。好吧,既然不存在那麼我就查查是什麼原因吧。首先,上傳了dede管理員密碼重設工