為什么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
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM